5
votes

I cannot understand how mock patch works and if does it able to solve my problem.

I have 3 files: communication with external interface (a.py), business logic (b.py) and tests (test.py). I want to patch external interface that is used by business logic while running tests.

a.py:

class SomeProductionClassINeedPatch(object):
    name = 'Production Class (communication with some external service)'
    def do_something(self):
        print '<some feature with external service>'

b.py:

import mock
from src.tmp.mocks.a import SomeProductionClassINeedPatch

class WorkingClass(object):
    def some_method_that_uses_external_class(self, *args):
        external = self._external
        external.do_something()

    @property
    def _external(self):
        if not hasattr(self, '_ext_obj' or not self._ext_obj):
            self._ext_obj = SomeProductionClassINeedPatch()
            print isinstance(self._ext_obj, mock.MagicMock) # False
        return self._ext_obj

b = WorkingClass()
b.some_method_that_uses_external_class()

test.py:

import mock
from src.tmp.mocks.b import WorkingClass    # class I want to test

@mock.patch('src.tmp.mocks.a.SomeProductionClassINeedPatch')
def test_some_method_of_working_class(external_mock=None, *args):
    o = WorkingClass()
    o.some_method_that_uses_external_class()        # external interface wasn't patched: <some feature with external service> - but I need mock here!
    print '<test> - '+str(isinstance(o._external, mock.MagicMock))  # False

test_some_method_of_working_class()

I expect that calling o.some_method_that_uses_external_class() in test.py will not actually use external interface, but mock object. But seems still actual object is used.

Also when I check instance of external interface object either in test.py or in b.py - I cannot make them to pass isinstance(object, MagicMock) check, it always return false. Even if I try to apply the same patch in b.py (as class decorator). What am I doing wrong?

I use python 2.7 and mock library 1.0 by Michael Foord if that matters.

1
I won't answer your question, but I recently found myself in a similar situation, used various hacks to make unittesting work, and then I stumbled upon the Clean Code Talks videos: youtube.com/watch?v=-FRm3VPhseI Where you can find some excellent design-patterns, which will teach you how to write easily testable code, and you won't need hacks to be able to test your code. I highly recommend them to everyone.andrean
thanks for video - appeared very helpful. Knew much important thingsSerge
don't mention, I felt like I achieved enlightenment after I watched it, and got a feeling like, omg I have to rewrite all the stuff I've done :)andrean

1 Answers

2
votes

As stated in Where to patch:

patch works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test.

In your example, the code using the object you want patched is in module b. When you call patch, the class has already been imported in module b, so patching a will have no effect on b. You need instead to path the object in b:

@mock.patch('src.tmp.mocks.b.SomeProductionClassINeedPatch')

This will give you the expected result, the first call from b is unpatched, while the second call from test used the mock object:

# python test.py
False
<some feature with external service>
True
<test> - True