Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python mock library - patching classes while unit testing

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.

like image 209
Serge Avatar asked Nov 03 '22 16:11

Serge


1 Answers

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
like image 72
Nicolas Cortot Avatar answered Nov 08 '22 07:11

Nicolas Cortot