Channels ▼
RSS

Testing

Using Mocks in Python


Listing Nine shows a slightly different use. In this one, I call the mocked method callFoo(), first without input, and then with the string "zort". The first assert passes (line 20), because callFoo() is not supposed to get any input. And the second assert fails (line 24) for obvious reasons.

Listing Nine

from mock import Mock

# The mock object
class Foo(object):
    # instance properties
    _fooValue = 123
    
    def callFoo(self):
        pass
    
    def doFoo(self, argValue):
        pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.callFoo.assert_called_with()
# assertion passes

mockFoo.callFoo("zort")
mockFoo.callFoo.assert_called_with()
# AssertionError: Expected call: callFoo()
# Actual call: callFoo('zort')

The next assert is assert_called_once_with(). Like assert_called_with(), this assert checks if the test subject called a mocked method correctly. But assert_called_once_with() will fire when the same method call happens more than once, whereas assert_called_with() will ignore multiple calls. Listing Ten shows how one might use the assert. Here, I make two calls to the mocked method callFoo(). On the first call (lines 19-20), the assert passes. But on the second call (lines 23-24), the assert fires, sending its error message to stdout.

Listing Ten

from mock import Mock

# The mock object
class Foo(object):
    # instance properties
    _fooValue = 123
    
    def callFoo(self):
        pass
    
    def doFoo(self, argValue):
        pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.callFoo.assert_called_once_with()
# assertion passes

mockFoo.callFoo()
mockFoo.callFoo.assert_called_once_with()
# AssertionError: Expected to be called once. Called 2 times.

The assert assert_any_call() checks if the test subject called a mocked method at any point of the test routine. This is regardless of how many other calls were made between the mocked method and the assert. Compare this with the previous two asserts, both of which check only the most recent call.

Listing Eleven shows the assert_any_call() assert at work: still the same mock object, with class Foo as its spec. The first call is to the method callFoo() (line 18), the next two to doFoo() (lines 19-20). Notice that doFoo() gets two different inputs.

Listing Eleven

<from mock import Mock

# The mock specification
class Foo(object):
    _fooValue = 123
    
    def callFoo(self):
        pass
    
    def doFoo(self, argValue):
        pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.doFoo("narf")
mockFoo.doFoo("zort")

mockFoo.callFoo.assert_any_call()
# assert passes

mockFoo.callFoo()
mockFoo.doFoo("troz")

mockFoo.doFoo.assert_any_call("zort")
# assert passes

mockFoo.doFoo.assert_any_call("egad")
# raises: AssertionError: doFoo('egad') call not found

The first assert_any_call() (line 22) passes even though two doFoo() calls separate the assert and callFoo(). The second assert (line 28) also passes even though a callFoo() separates it from the doFoo() in question (line 20). On the other hand, the third assert (line 31) fires, because none of the doFoo() calls used the string "egad" for input.

Finally, there is assert_has_calls(). This one looks at a sequence of method calls, checks if they are in the right order and with the right arguments. It takes two arguments: a list of expected method calls and an optional argument any_order. It fires when the test subject calls the wrong method, calls one method out of order, or gives a method the wrong input.

Listing Twelve demonstrates the assert_has_calls() assert. In lines 18-20, I make three method calls, providing input to two. Then, I prepare a list of expected calls (fooCalls) and pass this list to assert_has_calls() (lines 22-23). Since the list matches the method calls, the assert passes.

Listing Twelve

from mock import Mock, call

# The mock specification
class Foo(object):
    _fooValue = 123
    
    def callFoo(self):
        pass
    
    def doFoo(self, argValue):
        pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.doFoo("narf")
mockFoo.doFoo("zort")

fooCalls = [call.callFoo(), call.doFoo("narf"), call.doFoo("zort")]
mockFoo.assert_has_calls(fooCalls)
# assert passes

fooCalls = [call.callFoo(), call.doFoo("zort"), call.doFoo("narf")]
mockFoo.assert_has_calls(fooCalls)
# AssertionError: Calls not found.
# Expected: [call.callFoo(), call.doFoo('zort'), call.doFoo('narf')]
# Actual: [call.callFoo(), call.doFoo('narf'), call.doFoo('zort')]

fooCalls = [call.callFoo(), call.doFoo("zort"), call.doFoo("narf")]
mockFoo.assert_has_calls(fooCalls, any_order = True)
# assert passes

In line 26, I swapped the two doFoo() calls around. The first doFoo() gets "zort" for input, the second gets "narf". If I pass this fooCalls to assert_has_calls() (line 27), the assert fires. But if I pass a True to the any_order argument, the assert passes. This is because the assert now ignores the order in which the method calls were made.

Listing Thirteen demonstrates another use. To the list fooCalls, I added a nonexistent method dooFoo() (line 22). Then I passed fooCalls to assert_has_calls() (line 24). The assert fires, informing me that the expected call sequence did not match what actually happened. If I pass a True to the any_order argument (line 30), the assert names dooFoo() as the offending method call.

Listing Thirteen

from mock import Mock, call

# The mock specification
class Foo(object):
    _fooValue = 123
    
    def callFoo(self):
        pass
    
    def doFoo(self, argValue):
        pass

# create the mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo.callFoo()
mockFoo.doFoo("narf")
mockFoo.doFoo("zort")

fooCalls = [call.callFoo(), call.dooFoo("narf"), call.doFoo("zort")]

mockFoo.assert_has_calls(fooCalls)
# AssertionError: Calls not found.
# Expected: [call.callFoo(), call.dooFoo('narf'), call.doFoo('zort')]
# Actual: [call.callFoo(), call.doFoo('narf'), call.doFoo('zort')]

fooCalls = [call.callFoo(), call.dooFoo("narf"), call.doFoo("zort")]
mockFoo.assert_has_calls(fooCalls, any_order = True)
# AssertionError: (call.dooFoo('narf'),) not all found in call list

In both examples for assert_has_calls(), note the call keyword that appears before each method name. This keyword is for a helper object, one that marks out a method attribute in the mock object. To use the call keyword, make sure to import the helper from the mock module as follows:

from mock import Mock, call

Managing a Mock

A third set of methods from the Mock class allow you to control and manage your mock object. You can change how the mock behaves, alter some of its attributes, or restore the mock to its pre-test state. You can even change the response values for each mocked method or for the mock itself.

The method attach_mock() lets you add a second mock object to your mock. This method takes two arguments: the second mock object (aMock) and an attribute name (aName).

Listing Fourteen demonstrates how this is done. Here, I create two mock objects, mockFoo and mockBar, each one with a different spec (lines 25, 30). To mockFoo, I add mockBar using attach_mock() and the name "fooBar" (line 35). Once that is done, I can access the second mock and its attributes via the property fooBar (lines 46-53). And I can still access the attributes for the first mock, mockFoo (lines 40-43).

Listing Fourteen

from mock import Mock

# The mock object
class Foo(object):
    # instance properties
    _fooValue = 123
    
    def callFoo(self):
        print "Foo:callFoo_"
    
    def doFoo(self, argValue):
        print "Foo:doFoo:input = ", argValue

class Bar(object):
    # instance properties
    _barValue = 456
    
    def callBar(self):
        pass
    
    def doBar(self, argValue):
        pass

# create the first mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

# create the second mock object
mockBar = Mock(spec = Bar)
print mockBar
# returns: <Mock spec='Bar' id='2784400'>

# attach the second mock to the first
mockFoo.attach_mock(mockBar, 'fooBar')

# access the first mock's attributes
print mockFoo
# returns: <Mock spec='Foo' id='495312'>
print mockFoo._fooValue
# returns: <Mock name='mock._fooValue' id='428976'>
print mockFoo.callFoo()
# returns: <Mock name='mock.callFoo()' id='448144'>

# access the second mock and its attributes
print mockFoo.fooBar
# returns: <Mock name='mock.fooBar' spec='Bar' id='2788592'>
print mockFoo.fooBar._barValue
# returns: <Mock name='mock.fooBar._barValue' id='2788016'>
print mockFoo.fooBar.callBar()
# returns: <Mock name='mock.fooBar.callBar()' id='2819344'>
print mockFoo.fooBar.doBar("narf")
# returns: <Mock name='mock.fooBar.doBar()' id='4544528'>

The method configure_mock() lets you make wholesale changes to the mock object. Its sole argument is a sequence of key/value pairs, each key being the attribute you want changed. If the mock does not have the specified attribute, configure_mock() will add the attribute to the mock.

Listing Fifteen shows the configure_mock() method in action. Once again, I have a mock object (mockFoo) with class Foo for a spec and 555 for a return_value (line 13). Then with configure_mock(), I changed the return_value property to 999 (line 17). When I call mockFoo directly, I get 999 for a result, instead of the original 555.

Listing Fifteen

from mock import Mock

class Foo(object):
    # instance properties
    _fooValue = 123
    
    def callFoo(self):
        print "Foo:callFoo_"
    
    def doFoo(self, argValue):
        print "Foo:doFoo:input = ", argValue

mockFoo = Mock(spec = Foo, return_value = 555)
print mockFoo()
# returns: 555

mockFoo.configure_mock(return_value = 999)
print mockFoo()
# returns: 999

fooSpec = {'callFoo.return_value':"narf", 'doFoo.return_value':"zort", 'doFoo.side_effect':StandardError}
mockFoo.configure_mock(**fooSpec)

print mockFoo.callFoo()
# returns: narf
print mockFoo.doFoo("narf")
# raises: StandardError

fooSpec = {'doFoo.side_effect':None}
mockFoo.configure_mock(**fooSpec)
print mockFoo.doFoo("narf")
# returns: zort

Next, I prepare a dictionary object (fooSpec) into which I set the return values for two mocked methods and the side effect for doFoo() (line 21). I pass fooSpec into configure_mock(), taking care to prefix fooSpec with '**' (line 22). Invoking callFoo() now returns "narf" as a result; and invoking doFoo(), regardless of input, raises a StandardError signal (lines 24-27). If I alter fooSpec, setting the side-effect value for doFoo() to None, I get a result of "zort" when invoking doFoo() (lines 29-32).

The next method, mock_add_spec(), lets you add new attributes to the mock object. Its function is similar to the constructor argument spec, except mock_add_spec() works on an existing object, and it "erases" those attributes set by the constructor. The method takes two arguments: the attribute spec (aSpec) and a spec_set flag (aFlag). Again, the spec may be a list of strings or it may be a class. The added attributes are read-only by default, but passing a True to the spec_set flag make those same attributes writable.

Listing Sixteen demonstrates mock_add_spec() in action. The mock object (mockFoo) starts with attributes coming from class Foo (line 25). When I access two of the attributes (_fooValue and callFoo()), I get a result confirming their presence (lines 29-32)

Listing Sixteen

from mock import Mock

# The class interfaces
class Foo(object):
    # instance properties
    _fooValue = 123
    
    def callFoo(self):
        print "Foo:callFoo_"
    
    def doFoo(self, argValue):
        print "Foo:doFoo:input = ", argValue

class Bar(object):
    # instance properties
    _barValue = 456
    
    def callBar(self):
        pass
    
    def doBar(self, argValue):
        pass
    
# create the mock object
mockFoo = Mock(spec = Foo)

print mockFoo
# returns <Mock spec='Foo' id='507120'>
print mockFoo._fooValue
# returns <Mock name='mock._fooValue' id='2788112'>
print mockFoo.callFoo()
# returns: <Mock name='mock.callFoo()' id='2815376'>

# add a new spec attributes
mockFoo.mock_add_spec(Bar)

print mockFoo
# returns: <Mock spec='Bar' id='491088'>
print mockFoo._barValue
# returns: <Mock name='mock._barValue' id='2815120'>
print mockFoo.callBar()
# returns: <Mock name='mock.callBar()' id='4544368'>

print mockFoo._fooValue
# raises: AttributeError: Mock object has no attribute '_fooValue'
print mockFoo.callFoo()
# raises: AttributeError: Mock object has no attribute 'callFoo'

Then, I use mock_add_spec() to add class Bar to mockFoo (line 35). The mock object now assumes the attributes declared in class Bar (lines 39-42). If I access any Foo attribute, the mock object raises an AttributeError to signal their absence (lines 44-47).

The last method, resetMock(), puts the mock object back to its pre-test state. It clears the mock's call statistics and asserts. It does not clear the return_value and side_effect properties for both mock and its method attributes. Do this to reuse the mock and avoid the overhead of creating another mock.

Finally, you can assign a return value or side-effect to each method attribute. This you do through the accessors return_value and side_effect. For example, to make the method callFoo() return a value of "narf", use the return_value accessor as follows:

mockFoo.callFoo.return_value = "narf"

To give callFoo() the side-effect of TypeError, use the side_effect accessor as follows:

mockFoo.callFoo.side_effect = TypeError

To clear the side-effect, pass None to the accessor:

mockFoo.callFoo.side_effect = None

You can also use the same two accessors to change how the mock object responds to a factory call.

Statistics with a Mock

The last set of methods consists of accessors that track any calls made to a mock object. The accessor called returns a True when the mock gets a factory call, False otherwise. Consider the code in Listing Seventeen. After I create mockFoo, the called accessor returns a False result (lines 19-20). If I do a factory call, it returns a True result (lines 22-23). But what if I create a second mock object, then invoke a mocked method (callFoo(), line 30)? In that case, the called accessor will only give a False result (lines 31-32).

Listing Seventeen

from mock import Mock

# The mock object
class Foo(object):
    # instance properties
    _fooValue = 123
    
    def callFoo(self):
        print "Foo:callFoo_"
    
    def doFoo(self, argValue):
        print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

print mockFoo.called
# returns: False

mockFoo()
print mockFoo.called
# returns: True

mockFoo = Mock(spec = Foo)
print mockFoo.called
# returns: False

mockFoo.callFoo()
print mockFoo.called
# returns: False

The accessor call_count gives the number of times a mock object gets a factory call. Consider the code in Listing Eighteen. After I create mockFoo, call_count gives the expected result of 0 (lines 19-20). When I make a factory call to mockFoo, call_count increases by 1 (lines 22-24). When I invoke the mocked method callFoo(), call_count remains unchanged (lines 26-28). If I do a second factory call, call_count should increase by 1 more.

Listing Eighteen

from mock import Mock

# The mock object
class Foo(object):
    # instance properties
    _fooValue = 123
    
    def callFoo(self):
        print "Foo:callFoo_"
    
    def doFoo(self, argValue):
        print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo)
print mockFoo
# returns <Mock spec='Foo' id='507120'>

print mockFoo.call_count
# returns: 0

mockFoo()
print mockFoo.call_count
# returns: 1

mockFoo.callFoo()
print mockFoo.call_count
# returns: 1

The accessor call_args returns the arguments used in a factory call. Listing Nineteen demonstrates its action. For a newly created mock object (mockFoo), the call_args accessor gives a result of None (lines 17-21). If I make a factory call, passing "zort" for input, call_args reports it as "call('zort')" (line 23-25). Note the call keyword in the result. For a second factory call, without input, call_args returns "call()" (lines 27-29). A third factory call, with "troz" for input, gives the result "call('troz')" from call_args (lines 31-33). But when I invoke the mocked method callFoo(), the call_args accessor still returns "call('troz')" (lines 35-37).

Listing Nineteen

#!/usr/bin/python

from mock import Mock

# The mock object
class Foo(object):
    # instance properties
    _fooValue = 123
    
    def callFoo(self):
        print "Foo:callFoo_"
    
    def doFoo(self, argValue):
        print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo, return_value = "narf")
print mockFoo
# returns <Mock spec='Foo' id='507120'>
print mockFoo.call_args
# returns: None

mockFoo("zort")
print mockFoo.call_args
# returns: call('zort')

mockFoo()
print mockFoo.call_args
# returns: call()

mockFoo("troz")
print mockFoo.call_args
# returns: call('troz')

mockFoo.callFoo()
print mockFoo.call_args
# returns: call('troz')

The accessor call_args_list also reports the arguments used in a factory call. But while call_args returns the most recent arguments, call_args_list returns a list, with the first item being the earliest argument. Listing Twenty shows the accessor in action, using the same code in Listing Nineteen.

Listing Twenty

from mock import Mock

# The mock object
class Foo(object):
    # instance properties
    _fooValue = 123
    
    def callFoo(self):
        print "Foo:callFoo_"
    
    def doFoo(self, argValue):
        print "Foo:doFoo:input = ", argValue

# create the first mock object
mockFoo = Mock(spec = Foo, return_value = "narf")
print mockFoo
# returns <Mock spec='Foo' id='507120'>

mockFoo("zort")
print mockFoo.call_args_list
# returns: [call('zort')]

mockFoo()
print mockFoo.call_args_list
# returns: [call('zort'), call()]

mockFoo("troz")
print mockFoo.call_args_list
# returns: [call('zort'), call(), call('troz')]

mockFoo.callFoo()
print mockFoo.call_args_list
# returns: [call('zort'), call(), call('troz')]

The accessor method_calls reports the mocked method calls made by the test subject. Its result is a list object, each item showing the method name and its arguments.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video