How to mock calls to function that receives mutable object as parameter?

后端 未结 1 1808
情话喂你
情话喂你 2021-01-21 03:06

Consider example:

def func_b(a):
    print a

def func_a():
    a = [-1]
    for i in xrange(0, 2):
        a[0] = i
        func_b(a)

And test

1条回答
  •  迷失自我
    2021-01-21 03:30

    The following works (the importing mock from unittest is a Python 3 thing, and module is where func_a and func_b are):

    import mock
    from mock import call
    import copy
    
    class ModifiedMagicMock(mock.MagicMock):
        def _mock_call(_mock_self, *args, **kwargs):
            return super(ModifiedMagicMock, _mock_self)._mock_call(*copy.deepcopy(args), **copy.deepcopy(kwargs))
    

    This inherits from MagicMock, and redefines the call behaviour to deepcopy the arguments and keyword arguments.

    def test_a():
        from module import func_a
        with mock.patch('module.func_b', new_callable=ModifiedMagicMock) as func_b_mock:
            func_a()
            func_b_mock.assert_has_calls([call([0]), call([1])])
    

    You can pass the new class into patch using the new_callable parameter, however it cannot co-exist with autospec. Note that your function calls func_b with a list, so call(0), call(1) has to be changed to call([0]), call([1]). When run by calling test_a, this does nothing (passes).

    Now we cannot use both new_callable and autospec because new_callable is a generic factory but in our case is just a MagicMock override. But Autospeccing is a very cool mock's feature, we don't want lose it.

    What we need is replace MagicMock by ModifiedMagicMock just for our test: we want avoid to change MagicMock behavior for all tests... could be dangerous. We already have a tool to do it and it is patch, used with the new argument to replace the destination.

    In this case we use decorators to avoid too much indentation and make it more readable:

    @mock.patch('module.func_b', autospec=True)
    @mock.patch("mock.MagicMock", new=ModifiedMagicMock)
    def test_a(func_b_mock):
        from module import func_a
        func_a()
        func_b_mock.assert_has_calls([call([0]), call([1])])
    

    Or:

    @mock.patch("mock.MagicMock", new=ModifiedMagicMock)
    def test_a():
        with mock.patch('module.func_b') as func_b_mock:
            from module import func_a
            func_a()
            func_b_mock.assert_has_calls([call([0]), call([1])])
    

    0 讨论(0)
提交回复
热议问题