Channels ▼
RSS

Design

Patching Mocks in Python


Listing Two demonstrates the creation of a MagicMock instance. Here, class Foo serves as my test resource (lines 4-9). I pass Foo as the spec argument (line 12) to the MagicMock constructor. The resulting mock (fooMock) gets the same attributes as Foo. I can access each attribute, but not the responses defined by the class (lines 14-17). I can also invoke those methods inherited from the parent Mock (lines 24, 33).

Listing Two

from mock import MagicMock

# test resource
class Foo(object):
	myFoo = "foofoo"
	def doFoo(self):
		return("Foo:doFoo:")
	def callFoo(self, anArg):
		print "Foo:callFoo_:", anArg

# create mock object
fooMock = MagicMock(spec = Foo)

print fooMock
print fooMock.myFoo
print fooMock.doFoo()
print fooMock.callFoo("narf")
# == results ==
# <MagicMock spec='Foo' id='528464'>
# <MagicMock name='mock.myFoo' id='2812944'>
# <MagicMock name='mock.doFoo()' id='4837904'>
# <MagicMock name='mock.callFoo()' id='4742640'>

print fooMock.mock_calls
# returns:
# [call.__str__(),
#  call.myFoo.__str__(),
#  call.doFoo(),
#  call.doFoo().__str__(),
#  call.callFoo('narf'),
#  call.callFoo().__str__()]

print fooMock.method_calls
# returns:
# [call.doFoo(), call.callFoo('narf')]

Listing Three demonstrates another way to create a MagicMock instance. Once again, I have class Foo serving as spec. But I also pass a dictionary object (fooArgs), which holds two key/value pairs (line 12). The second pair sets the return value for the Foo method doFoo(). I also pass the attribute (fooTroz), assigning it the string value of "zort.troz".

Listing Three

from mock import MagicMock

# test resource
class Foo(object):
	myFoo = "foofoo"	
	def doFoo(self):
		return("Foo:doFoo:")
	def callFoo(self, anArg):
		print "Foo:callFoo_:", anArg

# create mock object
fooArgs = {'fooNarf':123, 'doFoo.return_value':"zort.zort"}
fooMock = MagicMock(spec = Foo, fooTroz = "zort.troz", **fooArgs)

print fooMock
print fooMock.myFoo
print fooMock.doFoo()
print fooMock.callFoo("narf")

print fooMock.mock_calls
# == results ==
# <MagicMock spec='Foo' id='427696'>
# <MagicMock name='mock.myFoo' id='428752'>
# zort.zort
# <MagicMock name='mock.callFoo()' id='3074416'>
# [call.__str__(),
#  call.myFoo.__str__(),
#  call.doFoo(),
#  call.callFoo('narf'),
#  call.callFoo().__str__()]

print fooMock.method_calls
# returns:
# [call.doFoo(), call.callFoo('narf')]

print fooMock.fooNarf
print fooMock.fooTroz
# == results ==
# 123
# zort.troz

As expected, fooMock still has the same attributes as Foo (lines 15-18). But invoking doFoo() returns the string value "zort.zort", the same value set by fooArgs. Also, fooMock gains two extra properties fooNarf and fooTroz (lines 36-37); the former is defined by fooArgs as well.

Using the Decorators

A patch is temporary by design. It attaches to the target resource before the start of a test, then "disappears" at the end of the test. Figure 2 describes the lifecycle of a typical patch. Here, we have our test resource (green) and test subject (grey). The patch decorator (orange) takes hold of the resource and applies the patch (red) onto it. When the test subject interacts with the patched resource, its interactions passes through the patch. The interaction ends, and the patch "peels" itself off the test resource.

patching mocks in python
Figure 2.

There are three ways to apply a patch. One way is to have the decorator create a patcher object. Then call the patcher's start() and stop() methods to set the patching scope. Listing Four demonstrates the patcher in action. Here, class Foo is my target (lines 4-9). In it are two methods doFoo() and callFoo(). The callFoo() method gets one input argument (anArg). Class Foo also has the property myFoo, which holds a string value.

Listing Four

from mock import patch, MagicMock

# the test resource
class Foo(object):
	myFoo = "Foo.myFoo"
    def doFoo(self):
        return("Foo:doFoo:")
    def callFoo(self, anArg):
        print "Foo:callFoo_:", anArg

# preparing to patch
fooPatch = patch('__main__.Foo')

# start patching
mockFoo = fooPatch.start()

# the patched resource
print mockFoo
print mockFoo()
print mockFoo.doFoo()
print mockFoo.callFoo("narf")
# == results ==
# <MagicMock name='Foo' id='429520'>
# <MagicMock name='Foo()' id='2985136'>
# <MagicMock name='Foo.doFoo()' id='3070096'>
# <MagicMock name='Foo.callFoo()' id='2589840'>

# changing a return value
mockFoo.doFoo.return_value = "Mock:Foo:doFoo"
print mockFoo.doFoo()
# returns: Mock:Foo:doFoo

print mockFoo.mock_calls
# returns:
# [call.__str__(),
#  call(),
#  call().__str__(),
#  call.doFoo(),
#  call.doFoo().__str__(),
#  call.callFoo('narf'),
#  call.callFoo().__str__(),
#  call.doFoo()]

# stop patching
fooPatch.stop()

# the original class
testFoo = Foo()
print testFoo.doFoo()
# returns: Foo:doFoo:

# the patched resource
print mockFoo
print mockFoo.doFoo()
# == results ==
# <MagicMock name='Foo' id='429520'>
# Mock:Foo:doFoo

print mockFoo.mock_calls
# returns:
# [call.__str__(),
#  call(),
#  call().__str__(),
#  call.doFoo(),
#  call.doFoo().__str__(),
#  call.callFoo('narf'),
#  call.callFoo().__str__(),
#  call.doFoo(),
#  call.__str__(),
#  call.doFoo()]

I pass class Foo to the core decorator (line 12). The decorator returns a patcher object (fooPatch), from which I invoke the start() method (line 15). In turn, fooPatch returns a MagicMock object (mockFoo), with attributes coming from class Foo. I could access each attribute, change the return value for doFoo(), and use mockFoo as I see fit (lines 18-33).

Things get interesting, however, after I invoke the stop() method (line 45). First, mockFoo remains valid. Its doFoo() method kept the return value defined within the patching zone. Class Foo is also unaffected. If I create a Foo instance (testFoo) and invoke its doFoo() method, I get the encoded value for that method (lines 48-49).

A second way to apply a patch is by context. This requires the use of a with-block, a standard feature found in Python 2.5 and newer. On Python 2.4, however, you have to import the with keyword by adding the following statement:

from __future__ import with_statement

Make sure this statement import is the first line in the script, right after the #! line (if there is one). Otherwise, the interpreter will raise a SyntaxError.

Listing Five demonstrates the use of the with-block. Class Foo is again my target. I invoke the core decorator (line 10) using the keyword with, and the decorator returns the patched resource as a MagicMock instance (mockFoo). I can access each attribute in mockFoo, set the return value for doFoo(), and do whatever I want to do with the mock (lines 12-24). Outside the with-block, mockFoo is still valid, and its doFoo() method still has the return value set within the block (line 39-40).

Listing Five

# needed for Python 2.4
from __future__ import with_statement
from mock import patch, MagicMock

# test resource
class Foo(object):
	#...see Listing 4...

# start patching
with patch('__main__.Foo') as mockFoo:
    # the patched resource
    print mockFoo
    print mockFoo()
    print mockFoo.doFoo()
    print mockFoo.callFoo("narf")
    # == results ==
    # <MagicMock name='Foo' id='446640'>
    # <MagicMock name='Foo()' id='3002224'>
    # <MagicMock name='Foo.doFoo()' id='3066704'>
    # <MagicMock name='Foo.callFoo()' id='2606928'>
    
    # changing a return value
    mockFoo.doFoo.return_value = "Mock:Foo:doFoo"
    print mockFoo.doFoo()
    # returns: Mock:Foo:doFoo
    
    print mockFoo.mock_calls
    # returns:
    # [call.__str__(),
    #  call(),
    #  call().__str__(),
    #  call.doFoo(),
    #  call.doFoo().__str__(),
    #  call.callFoo('narf'),
    #  call.callFoo().__str__(),
    #  call.doFoo()]

# the patched resource
print mockFoo
print mockFoo.doFoo()
# == results ==
# <MagicMock name='Foo' id='446640'>
# Mock:Foo:doFoo

A third approach to patching is to apply it by method. The decorator call is right before the test routine. The decorator's name gets an @ prefix; the routine gets a mock object as one of its arguments.

Listing Six demonstrates how this is done. Routine testFoo() serves as my test routine. Before it, you'll see that the core decorator, marked with an @ prefix. And mockFoo is the sole argument for testFoo() (lines 9-26).

Listing Six

from mock import patch, MagicMock

# test resource
class Foo(object):
	#...see Listing 4...

# start patching
@patch('__main__.Foo')
def testFoo(mockFoo):
    # the patched resource
    print mockFoo
    print mockFoo()
    print mockFoo.doFoo()
    print mockFoo.callFoo("narf")
    # returns:
    # <MagicMock name='Foo' id='448016'>
    # <MagicMock name='Foo()' id='2985328'>
    # <MagicMock name='Foo.doFoo()' id='3070288'>
    # <MagicMock name='Foo.callFoo()' id='2590032'>
    
    # changing the return value
    mockFoo.doFoo.return_value = "Mock:Foo:doFoo:"
    print mockFoo.doFoo()
    # returns: Mock:Foo:doFoo:
    
    print mockFoo.mock_calls
    # returns:
    # [call.__str__(),
    #  call(),
    #  call().__str__(),
    #  call.doFoo(),
    #  call.doFoo().__str__(),
    #  call.callFoo('narf'),
    #  call.callFoo().__str__(),
    #  call.doFoo()]

# run the test routine
testFoo()

# the patched resource
print mockFoo
# raises: NameError: name 'mockFoo' is not defined

Here again, I get a MagicMock instance, whose attributes are from class Foo (lines 11-14). I can access the attributes, change one or more return values, and use the mock object appropriately. Outside the scope of testFoo(), however, mockFoo ceases to be valid. Attempts to use mockFoo will raise a NameError (line 41).

Listing Seven shows a variation of testFoo().

Listing Seven

from mock import patch, MagicMock

# the test resource
class Foo(object):
    def doFoo(self):
        return("Foo:doFoo:")
    def callFoo(self, anArg):
        print "Foo:callFoo_:", anArg

# start patching
@patch('__main__.Foo')
def testFoo(argFoo, mockFoo):
    # the test argument
    print argFoo
    # returns: narf
    
    # the patched resource
    print mockFoo
    print mockFoo()
    print mockFoo.doFoo()
    print mockFoo.callFoo("narf")
    # == results ==
    # <MagicMock name='Foo' id='448016'>
    # <MagicMock name='Foo()' id='2985328'>
    # <MagicMock name='Foo.doFoo()' id='3070288'>
    # <MagicMock name='Foo.callFoo()' id='2590032'>
    
    # changing the return value
    mockFoo.doFoo.return_value = "Mock:Foo:doFoo:"
    print mockFoo.doFoo()
    # returns: Mock:Foo:doFoo:
    
    print mockFoo.mock_calls
    # returns: 
    # [call.__str__(),
    #  call(),
    #  call().__str__(),
    #  call.doFoo(),
    #  call.doFoo().__str__(),
    #  call.callFoo('narf'),
    #  call.callFoo().__str__(),
    #  call.doFoo()]

# run the test routine
testFoo("narf")

# the patched resource
print mockFoo
# raises: NameError: name 'mockFoo' is not defined


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