简体   繁体   中英

python mock: replacing a class method

Consider the following code (not a very good design, but that's the point):

class A(object):
    def __init__(self,filepath):
        self._access_file_system(filepath)

    def get(self):
        return self._result_dict


class B(object):
    def __init__(self,filepath1,filepath2):
        self._filepath1 = filepath1
        self._filepath2 = filepath2

    def foo(self):
        a1 = A(self._filepath1).get()
        a2 = A(self._filepath2).get()
        return a1['result']==a2['result']

Now, if I want to test B.foo() , I need to mock A (as it accesses the file-system inside the constructor).

To write a test that will make sure B.foo() returns False in case a1.get() and a2.get() provide different values, I also need to mock B.get() .

So, the test function should probably look like the following:

import mock
mock_get = mock.MagicMock(side_effect=[{'result': 0}, {'result': 1}])

@mock.patch('__main__.A')
def test_foo(MockA):

    b = B('/file1','/file2')
    res = b.foo()
    assert res
    MockA.assert_any_call('/file1')
    MockA.assert_any_call('/file2')

    #Doesn't work -
    #the assignment doesn't propagate into the objects instantiated inside foo()
    #A.get = mock_get

    #The assigned method propagates into the class definition,
    #so it works - BUT WHY?!
    a = A(None)
    a.get = mock_get

    b = B('/file1', '/file2')
    res = b.foo()
    assert not res

Now, the strange point - as may be seen from the comments inside the code, if we assign mock_get to the class, it won't propagate, but if we create an instance and assign to it, it propagates to other instances of the class.

I suppose this behavior is related to internal mechanisms of mock , so it's important for me to understand it, to make proper usage of this library with all it's rich functionality.

So, does anyone has a clue?

On the first case I can not see anywhere that you are patching the get method. You should assign the mock value to the get method of A before B is called. For instance why does the following test fail?:

import mock
mock_get = mock.MagicMock(side_effect=[{'result': 0}, {'result': 1}])

@mock.patch('__main__.A')
def test_foo(MockA):

    MockA.get = mock_get

    b = B('/file1','/file2')
    res = b.foo()
    assert not res
    MockA.assert_any_call('/file1')
    MockA.assert_any_call('/file2')

The reason for the previous behaviour is that we are forgetting to patch the return value of the object (A), in this case MockA , instead of the object itself ( MockA ). The A object is the result of instantiating the A class and you should access the method throgh the return_value of the A class. In your example it would be something similar to this:

import mock
mock_get = mock.MagicMock(side_effect=[{'result': 0}, {'result': 1}])

@mock.patch('__main__.A')
def test_foo(MockA):

    MockA.return_value.get = mock_get
    b = B('/file1','/file2')
    res = b.foo()

    assert res
    MockA.assert_any_call('/file1')
    MockA.assert_any_call('/file2')

You can check some of the following posts for more info on common python unit testing pitfalls:

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM