Channels ▼
RSS

Testing

Using Mocks in Python


Listing Twenty-one demonstrates method_calls in action. With a newly created mockFoo, method_calls returns an empty list (lines 15-19). The same also happens when I do a factory call (lines 21-23). When I invoke the mocked method callFoo(), method_calls returns a list object with one entry (lines 25-27). When I invoke doFoo(), passing "narf" for input, method_calls returns a list with two items (lines 29-31). Notice how each method name appears in the order of its invocation.

Listing Twenty-one

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 = "poink")
print mockFoo
# returns <Mock spec='Foo' id='507120'>
print mockFoo.method_calls
# returns []

mockFoo()
print mockFoo.method_calls
# returns []

mockFoo.callFoo()
print mockFoo.method_calls
# returns: [call.callFoo()]

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

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

The last accessor mock_calls reports all calls made by the test subject to the mock object. The result is again a list, but now showing both factory and method calls. Listing Twenty-two demonstrates the accessor in action, using the same code in Listing Twenty-one.

Listing Twenty-two

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 = "poink")
print mockFoo
# returns <Mock spec='Foo' id='507120'>

print mockFoo.mock_calls
# returns []

mockFoo()
print mockFoo.mock_calls
# returns [call()]

mockFoo.callFoo()>
print mockFoo.mock_calls
# returns: [call(), call.callFoo()]

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

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

Testing with a Mock

Data type, model, or node — these are just some of the roles a mock object might assume. But how does a mock fit inside a unit test setup? Let us take a look, using a simplified setup taken from the Martin Fowler article Mocks Aren't Stubs.

In the test setup are three classes (Figure 4). The Order class is the test subject. It models a single item purchase order, which it fills from a data source. The Warehouse class is the test resource. It contains a sequence of key/value pairs, the key being the item name, the value being the available quantity. And the OrderTest class is the test case itself.

Python
Figure 4.

Listing Twenty-three describes the Order class. The class declares three properties: the item name (_orderItem), the requested quantity (_orderAmount) and the filled quantity (_orderFilled). Its constructor takes two arguments (lines 8-18), which it uses to populate the properties _orderItem and _orderAmount. Its __repr__() method returns a summary of the purchase order (lines 21-24).

Listing Twenty-three

class Order(object):
    # instance properties
    _orderItem = "None"
    _orderAmount = 0
    _orderFilled = -1
    
    # Constructor
    def __init__(self, argItem, argAmount):
        print "Order:__init__"
        
        # set the order item
        if (isinstance(argItem, str)):
            if (len(argItem) > 0):
                self._orderItem = argItem
        
        # set the order amount
        if (argAmount > 0):
            self._orderAmount = argAmount
        
    # Magic methods
    def __repr__(self):
       # assemble the dictionary
        locOrder = {'item':self._orderItem, 'amount':self._orderAmount}
        return repr(locOrder)
    
    # Instance methods
    # attempt to fill the order
    def fill(self, argSrc):
        print "Order:fill_"
        
        try:
            # does the warehouse has the item in stock?
            if (argSrc is not None):
                if (argSrc.hasInventory(self._orderItem)):
                    # get the item
                    locCount =    argSrc.getInventory(self._orderItem, self._orderAmount)
                
                    # update the following property
                    self._orderFilled = locCount
                else:
                    print "Inventory item not available"
            else:
                print "Warehouse not available"
        except TypeError:
            print "Invalid warehouse"
    
    # check if the order has been filled
    def isFilled(self):
        print "Order:isFilled_"
        return (self._orderAmount == self._orderFilled)

The Order class defines two instance methods. The fill() method gets a data source (argSrc) for an argument. It checks if the source is valid and if it has the item in question (line 33-34). It submits a withdrawal request and updates _orderFilled with the actual quantity withdrawn (lines 36-39). The isFilled() method returns a True when both _orderAmount and _orderFilled have the same values (lines 48-50).

Listing Twenty-four describes the Warehouse class. It is an abstract class, declaring properties and method interfaces, but not defining the methods themselves. The property _houseName holds the warehouse's name, while _houseList holds its inventory. Accessors exists for these two properties.

Listing Twenty-four

class Warehouse(object):    
    # private properties
    _houseName = None
    _houseList = None
        
    # accessors
    def warehouseName(self):
        return (self._houseName)
    
    def inventory(self):
        return (self._houseList)
    
    
    # -- INVENTORY ACTIONS
    # set up the warehouse
    def setup(self, argName, argList):
    	pass
    
    # check for an inventory item
    def hasInventory(self, argItem):
        pass
    
    # retrieve an inventory item
    def getInventory(self, argItem, argCount):
        pass
        
    # add an inventory item
    def addInventory(self, argItem, argCount):
        pass

The class declares four method interfaces. The method setup() takes two arguments, which are meant to update the two properties. The method hasInventory() gets an item name and returns a True if said item is in the inventory. The method getInventory() gets an item name and quantity. It tries to deduct that quantity from the inventory, and returns what was deducted successfully. The method addInventory() also gets an item name and quantity. It is suppose to update _houseList with those two arguments.

In Listing Twenty-five is the test case itself, OrderTest. Its one property, fooSource, holds the mock object needed by the Order class. The setUp() method identifies the test routine to be executed (lines 14-16), then creates and configures the mock object (lines 21-34). And the tearDown() method prints an empty line to stdout.

Listing Twenty-five

import unittest
from mock import Mock, call

class OrderTest(unittest.TestCase):
    # declare the test resource
    fooSource = None
    
    # preparing to test
    def setUp(self):
        """ Setting up for the test """
        print "OrderTest:setUp_:begin"
        
        # identify the test routine
        testName = self.id().split(".")
        testName = testName[2]
        print testName
        
        # prepare and configure the test resource
        if (testName == "testA_newOrder"):
            print "OrderTest:setup_:testA_newOrder:RESERVED"
        elif (testName == "testB_nilInventory"):
            self.fooSource = Mock(spec = Warehouse, return_value = None)
        elif (testName == "testC_orderCheck"):
            self.fooSource = Mock(spec = Warehouse)
            self.fooSource.hasInventory.return_value = True
            self.fooSource.getInventory.return_value = 0
        elif (testName == "testD_orderFilled"):
            self.fooSource = Mock(spec = Warehouse)
            self.fooSource.hasInventory.return_value = True
            self.fooSource.getInventory.return_value = 10
        elif (testName == "testE_orderIncomplete"):
            self.fooSource = Mock(spec = Warehouse)
            self.fooSource.hasInventory.return_value = True
            self.fooSource.getInventory.return_value = 5
        else:
            print "UNSUPPORTED TEST ROUTINE"
    
    # ending the test
    def tearDown(self):
        """Cleaning up after the test"""
        print "OrderTest:tearDown_:begin"
        print ""
    
    # test: new order
    # objective: creating an order
    def testA_newOrder(self):
        # creating a new order
        testOrder = Order("mushrooms", 10)
        print repr(testOrder)
        
        # test for a nil object
        self.assertIsNotNone(testOrder, "Order object is a nil.")
        
        # test for a valid item name
        testName = testOrder._orderItem
        self.assertEqual(testName, "mushrooms", "Invalid item name")
        
        # test for a valid item amount
        testAmount = testOrder._orderAmount
        self.assertGreater(testAmount, 0, "Invalid item amount")
    
    # test: nil inventory
    # objective: how the order object handles a nil inventory
    def testB_nilInventory(self):
        """Test routine B"""
        # creating a new order
        testOrder = Order("mushrooms", 10)
        print repr(testOrder)
        
        # fill the order
        testSource = self.fooSource()
        testOrder.fill(testSource)
        
        # print the mocked calls
        print self.fooSource.mock_calls
        
        # check the call history
        testCalls = [call()]
        self.fooSource.assert_has_calls(testCalls)
    
    # ... continued in the next listing

The OrderTest class has five test routines. All five start by creating an instance of the Order class. The routine testA_newOrder() tests if the Order object is valid and if it holds the right data (lines 46-60). The routine testB_nilWarehouse() creates a null mock and passes that to the Order object's fill() method (lines 64-79). It checks the mock's call history, making sure only a factory call has occurred.

The routine testC_orderCheck() (Listing Twenty-six) tests how the Order object reacts to a missing inventory item. Initially, fooSource responds with a True with its hasInventory() method and a 0 with getinventory(). The test routine checks if the order is not fulfilled and if the right mocked method is called (lines 16-19). Then the routine creates a new Order object, this time for a different item. The mock fooSource is set to respond with a False with its hasInventory() method (line 27). Again, the routine checks if the order is not filled and if the right mocked method is called (lines 34-37). Note the use of the reset_mock() method to restore fooSource to its pre-test state (line 28).

Listing Twenty-six

class OrderTest(unittest.TestCase):
    # ... see previous listing
    
    # test: checking the inventory
    # objective: does the order object check for inventory?
    def testC_orderCheck(self):
        """Test routine C"""
        # creating a test order
        testOrder = Order("mushrooms", 10)
        print repr(testOrder)
        
        # perform the test
        testOrder.fill(self.fooSource)
        
        # perform the checks
        self.assertFalse(testOrder.isFilled())
        self.assertEqual(testOrder._orderFilled, 0)
        
        self.fooSource.hasInventory.assert_called_once_with("mushrooms")
        print self.fooSource.mock_calls
        
        # creating another order
        testOrder = Order("cabbage", 10)
        print repr(testOrder)
        
        # reconfigure the test resource
        self.fooSource.hasInventory.return_value = False
        self.fooSource.reset_mock()
        
        # perform the test
        testOrder.fill(self.fooSource)
        
        # perform the checks
        self.assertFalse(testOrder.isFilled())
        self.assertEqual(testOrder._orderFilled, -1)
        
        self.fooSource.hasInventory.assert_called_once_with("cabbage")
        print self.fooSource.mock_calls
    
    # ... continued in the next listing

Test routine testD_orderFilled() (Listing Twenty-seven) simulates a successful order transaction. Here, fooSource responds with a True with its hasInventory() method and returns 10 with getinventory(). The routine passes the mock to the fill() method, then checks if the order has been filled (lines 17-18). It also checks whether the right mocked methods were called in the right order and with the right arguments (lines 20-24).

Listing Twenty-seven

class OrderTest(unittest.TestCase):
    # ... see previous listing
    
    # test: fulfilling an order
    # objective: how does the order object behave with a successful transaction
    def testD_orderFilled(self):
        """Test routine D"""
        # creating a test order
        testOrder = Order("mushrooms", 10)
        print repr(testOrder)
        
        # perform the test
        testOrder.fill(self.fooSource)
        print testOrder.isFilled()
        
        # perform the checks
        self.assertTrue(testOrder.isFilled())
        self.assertNotEqual(testOrder._orderFilled, -1)
        
        self.fooSource.hasInventory.assert_called_once_with("mushrooms")
        self.fooSource.getInventory.assert_called_with("mushrooms", 10)
        
        testCalls = [call.hasInventory("mushrooms"), call.getInventory("mushrooms", 10)]
        self.fooSource.assert_has_calls(testCalls)
    
    # ... continued in the next listing

Test routine testE_orderIncomplete() (Listing Twenty-eight) simulates an incomplete transaction. In this one, fooSource responds with a True with its hasInventory(), but with a 5 with getinventory(). The routine passes the mock to the fill() method, then checks for an incomplete order (lines 17-18). And it also checks if the right mocked methods were called in the right order and with the right arguments (lines 20-25).

Listing Twenty-eight

class OrderTest(unittest.TestCase):
    # ... see previous listing
    
    # test: fulfilling an order
    # objective: how does the order object behave with an incomplete transaction
    def testE_orderIncomplete(self):
        """Test routine E"""
        # creating a test order
        testOrder = Order("mushrooms", 10)
        print repr(testOrder)
        
        # perform the test
        testOrder.fill(self.fooSource)
        print testOrder.isFilled()
        
        # perform the checks
        self.assertFalse(testOrder.isFilled())
        self.assertNotEqual(testOrder._orderFilled, testOrder._orderAmount)
        
        self.fooSource.hasInventory.assert_called_once_with("mushrooms")
        self.fooSource.getInventory.assert_called_with("mushrooms", 10)
        print self.fooSource.mock_calls
        
        testCalls = [call.hasInventory("mushrooms"), call.getInventory("mushrooms", 10)]
        self.fooSource.assert_has_calls(testCalls)

Conclusion

Mocks let us simulate resources that are either unavailable or too unwieldy for unit testing. We can configure a mock on the fly, change how it behaves or responds in a particular test, or get it to throw errors and exceptions on cue.

In this article, we looked at how a unit test setup benefits from a mock. We learned how a mock differs from a fake or a stub. We learned how to create a mock on Python, how to manage it and how to track its behavior with asserts. And we examined a simple mock at work in a basic test case.Feel free to try out this test setup and observe its results. Feel free to tweak the test routines presented here, and experiment with how those tweaks affect the test.

References

Alex Marandon. Python Mock Gotchas.

Grig Gheorghiu. (2009). Python Mock Testing Techniques and Tools. Python Magazine

Insomihack. Python Unit Testing with Mock.

Matt Cottingham. Python: Injecting Mock Objects for Powerful Testing.

Michael Foord. (2012). Mock Documentation, Release 1.0.1. [PDF]

Martin Fowler. Mocks Aren't Stubs.

Naftuli Tzvi Kay. An Introduction to Mocking in Python.

Steve Freeman, Tim Mackinnon, et al. (2004). Mock Roles, Not Objects [PDF]. OOPLSA 2004


José R.C. Cruz is a freelance technical writer based in British Columbia. He frequently contributes articles to Dr. Dobb's, MacTech, and REALStudio Developer.


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