How to capture field value when a method of a class is called in Python?

146 Views Asked by At

I have a weird 3rd party library that requires me doing the following (A is imported from that library):

def foo(my_props):
  a = A()
  a.props = my_props
  a.post()

So what I want is to make sure that when a.post() is called, its props are set correctly. Note that this is a greatly simplified example so it's apparent it would be easy to mock foo instead. Unfortunately there is much more into it (like my_props may be modified in foo before calling a.post).

Worth noting that from looking into the source code of that import, props is not a class property. It's a simple dict, class field, set something like self.props = ... at random places in the class A.

So how can I setup my mocking to accomplish this feast? I am not even interested in post itself. I need to know how many times delete is called and what values props were set at that time.

EDIT: re-enforcing @hspandher response, ended up doing the following because unfortunately call_args stayed empty, which I hoped I could have analyzed after the call.

@mock.patch('A.props', return_value=mock.PropertyMock())
def test_foo(self, mock_props):
    call_args = []
    def capture(*args, **kwargs):
        call_args.append(args)

    mock_props.__set__ = functools.partial(capture)

    a = A()
    a.foo()

    # analyze call_args...
2

There are 2 best solutions below

1
On BEST ANSWER

For this you don't even need mocking, lets say foo is defined in file code.py, in test file code should be like this from code import A A.props = <mockvalue> and then your testing code. But if you to want to do something little more sophisticated like mocking post , I suggest you use python mock or fudge library

0
On

If you can initialize a outside of foo and pass as argument so the actual call looks like this:

def foo(my_props, obj):
    obj.props = my_props
    obj.post()

foo({'bar': 'baz'}, a)

Then you could test it like this:

def test_foo():
    a = mock.create_autospec(A)
    foo(1, a)
    assert a.props == 1
    assert a.mock_calls == [mock.call.post()]

Checking if props are set before calling post is a little more work.
You will need to convert foo to a class Foo:

class Foo(object):

    def foo(self, my_props, obj):
        self._setup_props(my_props, obj)
        self._post(obj)

    def _setup_props(self, my_props, obj):
        obj.props = my_props

    def _post(self, obj):
        obj.post()

Now you can mock Foo itself and it's foo method to check the mock_calls order:

>>> f = mock.Mock(spec=Foo, foo=Foo.foo)
>>> f.foo(f, 1, 2)
>>> f.mock_calls
[call._setup_props(1, 2), call._post(2)]