5

I've some code where a call to the method update() changes the values of some instance variables. I use the changed value to leave a loop. Here is simplified example of my code:

def do_stuff(self):
    # get a new instance of A
    a = get_a()
    while True:
        a.update()
        if a.state == 'state':
            break

This a simple version of the class (I can't change the class, because it's 3rd party library):

class A(object):
    def __init__(self):
        self.state = ''

    def update(self):
        # call to external system
        self.state = extern_func()

Now I want to test my function do_stuff() by mocking class A. To test every aspect of the function I want to have all the different values of state and it should changing after each call of a.update() (iterate over the different states).

I started with this set up for my unit test:

from mock import Mock, patch
import unittest

class TestClass(unittest.TestClass):

    @patch('get_a')
    def test_do_stuff(self, mock_get_a):
         mock_a = Mock(spec=A)
         mock_get_a.return_value = mock_a

         # do some assertions

Can I achieve that kind behaviour with Mock? I know that Mock has side_effect to return different value for consecutive function calls. But I can't figure out a way to change a value of an instance variable after a function call?

3
  • 1
    Is it possible to keep A as it is and mock the external function? That seems the easiest way to iterate over the states as you can indeed use side_effect to have it return each of the states. Commented Sep 13, 2014 at 0:52
  • @SimeonVisser: What you are proposing is possible, but goes against the recommendation Where to patch. Commented Sep 15, 2014 at 17:37
  • It doesn't go against that recommendation; in fact, it follows it as you need to patch the object in the location where it's used. That only changes depending on how external_func is imported (but we can't see that in your example). If it would be import mymodule and mymodule.external_func() then you'd need to change the patching to include mymodule. Commented Sep 15, 2014 at 22:35

1 Answer 1

1

Setup:

from mock import Mock, MagicMock, patch

sep = '***********************\n'

# non-mock mocks
def get_a():
    return A()
def extern_func():
    return 'foo'

def do_stuff(self):
    # get a new instance of A
    a = get_a()
    while True:
        a.update()
        print a.state
        if a.state == 'state':
            break

class A(object):
    def __init__(self):
        self.state = ''

    def update(self):
        # call to external system
        self.state = extern_func()

As @Simeon Viser mentioned, mocking extern_func would work:

print sep, 'patch extern'
mock = MagicMock(side_effect = [1,2,3,4, 'state'])
@patch('__main__.extern_func', mock)
def foo():
    do_stuff(3)
foo()

>>> 
***********************
patch extern
1
2
3
4
state

side_effect can be a function and you can mock the unbound method A.update using the auto_spec = True argument.

Using a context manager:

print sep, 'patch update context manager call do_stuff'
def update_mock(self):
    self.state = mock()
mock = MagicMock(side_effect = [1,2,3,4, 'state'])
with patch.object(A, 'update', autospec = True) as mock_update:
    mock_update.side_effect = update_mock
    do_stuff(3)

>>>
***********************
patch update context manager call do_stuff
1
2
3
4
state

Using a decorator:

print sep, 'patch update decorator test_do_stuff'
def update_mock(self):
    self.state = mock()
mock = MagicMock(side_effect = [1,2,3,4, 'state'])
@patch.object(A, 'update', autospec = True, side_effect = update_mock)
def test_do_stuff(self):
    do_stuff(3)
test_do_stuff()

>>>
***********************
patch update decorator test_do_stuff
1
2
3
4
state

Caveat: I've never written comprehensive unit tests and only recently started reading the mock docs, so even though I seemed to have made this work I can't comment on it's efficacy in your testing scheme. Edits welcome.

Sign up to request clarification or add additional context in comments.

2 Comments

As mentioned in the comment to @SimeonVisser your solution goes against the recommendation Where to patch. But so far it looks like the only possible solution. I will try to get my test running and let you know about my findings.
... That isn't a recommendation, it is describing how you reference the object you are patching - you have to follow those instructions. In my examples, the objects being patched and the test/mocking code are in the same module.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.