Channels ▼
RSS

Design

Patching Mocks in Python


In Listing Twenty-seven, is the setUp() method for the OrderTest class. It starts with a call to the inherited method id(), and uses the result to identify the test routine (lines 13-14). If the routine's name is not "testA_newOrder" (line 18), setUp() creates a Warehouse mock and stores the mock into the property fooSource (line 21). Then it prepares fooSource for the test routine in question (lines 24-29).

Listing Twenty-seven

class OrderTest(unittest.TestCase):
    """Test case for the Order class"""
    
    # 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 "Test routine::", testName
        
        # prepare and configure the test resource
        if (testName == "testA_newOrder"):
            print "OrderTest:setup_:testA_newOrder:RESERVED"
        else:
            self.fooSource = Mock(spec = Warehouse, return_value = None)
            self.fooSource.hasInventory.return_value = True
            
            if (testName == "testC_orderCheck"):
                self.fooSource.getInventory.return_value = 0
            elif (testName == "testD_orderFilled"):
                self.fooSource.getInventory.return_value = 10
            elif (testName == "testE_orderIncomplete"):
                self.fooSource.getInventory.return_value = 5

But this setUp() method has several problems. First, I need to know beforehand what each test routine expects of the mock. This is not possible if I am still working out what the routine is suppose to do. Second, if the test routine has two or more subtests, the setUp() can only prepare the mock for the first one. I must reconfigure the mock for the other subtests, then make sure to restore the mock before the next test routine. If there are half a dozen or more test routines, the setUp() method can become unmanageable.

A better approach is to let the test routines prepare their own mocks. The setUp() method then boils down into the form shown in Listing Twenty-eight. Gone is the fooSource property; gone the code needed to prepare the Warehouse mock. All that is left is for setUp() to report the upcoming test routine (line 12).

Listing Twenty-eight

class OrderTest(unittest.TestCase):
    """Test case for the Order class"""
    
    # 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 "Test routine::", testName
    
    # ending the test
    def tearDown(self):
        """Cleaning up after the test"""
        print "OrderTest:tearDown_:begin"
        print ""
    
    # ... continued in Listing Twenty-nine

In Listing Twenty-nine is the test routine testB_nilInventory(). Here, I used the Mock class to create the Warehouse mock (testMock). I passed the Warehouse class as the spec and set the mock's return_value attribute to None (line 10). I created an Order instance (testOrder) and passed testMock to the fill() method (lines 13-18). I also printed the attribute mock_calls to stdout and examined the call history (line 21).

Listing Twenty-nine

class OrderTest(unittest.TestCase):
    """Test case for the Order class"""
    
    # ...see Listing Twenty-eight
    
    # test: nil inventory
    # objective: how the order object handles a nil inventory
    def testB_nilInventory(self):
        # create the test resource
        testMock = Mock(spec = Warehouse, return_value = None)
        
        # create a new order
        testOrder = Order("mushrooms", 10)
        print str(testOrder)
        
        # fill the order
        testSource = testMock()
        testOrder.fill(testSource)
        
        # print the mocked calls
        print testMock.mock_calls
    
    # ... continued in Listing Thirty

In Listing Thirty is the test routine testC_orderCheck(). The routine has two subtests, each one with its own Warehouse mock (testMock), both using the core decorator with the with keyword. Both subtests use the same asserts to check the behavior of the Order instance (testOrder).

Listing Thirty

class OrderTest(unittest.TestCase):
    """Test case for the Order class"""
    
    # ... see Listing Twenty-nine
    
    # test: checking the inventory
    # objective: does the order object check for inventory?
    def testC_orderCheck(self):
        # perform the first test
        testOrder = Order("mushrooms", 10)
        print str(testOrder)
        
        mockArgs = {'hasInventory.return_value':True, 'getInventory.return_value':0}
        with patch('__main__.Warehouse', **mockArgs) as testMock:
            testOrder.fill(testMock)
            
            # perform the checks 
            self.assertFalse(testOrder.isFilled())
            self.assertEqual(testOrder._orderFilled, 0)
            
            testMock.hasInventory.assert_called_once_with("mushrooms")
            print testMock.mock_calls
        
        # perform the second test
        testOrder = Order("cabbage", 10)
        print str(testOrder)
        
        with patch('__main__.Warehouse') as testMock:
        	# configure the test resource
            testMock.hasInventory.return_value = False
            testMock.getInventory.return_value = 0
            
            # perform the test
            testOrder.fill(testMock)
            
            # perform the checks            
            self.assertFalse(testOrder.isFilled())
            self.assertEqual(testOrder._orderFilled, -1)
            
            testMock.hasInventory.assert_called_once_with("cabbage")
            print testMock.mock_calls
    
    # ... continued in Listing Thirty-one

For the first subtest (lines 13-22), I define the mock's method attributes as a key/value sequence (mockArgs). I then pass the Warehouse class and mockArgs as arguments to the decorator. For the second subtest (lines 28-41), I pass only Warehouse as the decorator's argument. Then within the with-block, I alter the same method attributes (lines 30-31).

Listing Thirty-one shows the updated routine testD_orderFilled(). For this one, I have the core decorator before the routine name (line 8), and I added testMock as a test argument (line 9). Inside the routine, I configured testMock, then passed it to the fill() method of testOrder (lines 15-19). I also checked the behavior of testOrder with asserts (lines 23-27).

Listing Thirty-one

class OrderTest(unittest.TestCase):
    """Test case for the Order class"""
    
    # ... see Listing Thirty        
    
    # test: filling the order
    # objective: is the order completely filled?
    @patch('__main__.Warehouse')
    def testD_orderFilled(self, testMock):
        # creating a test order
        testOrder = Order("mushrooms", 10)
        print str(testOrder)
        
        # configure the mock
        testMock.hasInventory.return_value = True
        testMock.getInventory.return_value = 10
        
        # perform the test
        testOrder.fill(testMock)
        print testOrder.isFilled()
        
        # perform the checks
        self.assertTrue(testOrder.isFilled())
        self.assertNotEqual(testOrder._orderFilled, -1)
        
        testMock.hasInventory.assert_called_once_with("mushrooms")
        testMock.getInventory.assert_called_with("mushrooms", 10)
        print testMock.mock_calls
    
    # ... continued in Listing Thirty-two

Listing Thirty-two shows the updated routine testE_orderIncomplete().

Listing Thirty-two

class OrderTest(unittest.TestCase):
    """Test case for the Order class"""
    
    # ... see Listing Thirty-one        
    
    # test: filling the order
    # objective: is the order partially filled?
    def testE_orderIncomplete(self):
        # creating a test order
        testOrder = Order("mushrooms", 10)
        print str(testOrder)
        
        # prepare the test resource
        mockArgs = {'hasInventory.return_value':True, 'getInventory':5}
        testPatch = patch('__main__.Warehouse', **mockArgs)        
        testMock = testPatch.start()
        
        # perform the first test
        testOrder.fill(testMock)
        print testOrder.isFilled()
        
        # perform the checks
        self.assertFalse(testOrder.isFilled())
        self.assertNotEqual(testOrder._orderFilled, testOrder._orderAmount)
        
        testMock.hasInventory.assert_called_once_with("mushrooms")
        testMock.getInventory.assert_called_with("mushrooms", 10)
        print testMock.mock_calls
        testMock = testPatch.stop()

        # prepare mock resource
        mockPatch = patch('__main__.Warehouse')
        testMock = mockPatch.start()
        testMock.hasInventory.return_value = False
        testMock.getInventory.return_value = 0
        
        # perform the test
        testOrder.fill(testMock)
        print testOrder.isFilled()
        
        # perform the checks
        self.assertFalse(testOrder.isFilled())
        self.assertNotEqual(testOrder._orderFilled, testOrder._orderAmount)
        
        testMock.hasInventory.assert_called_once_with("mushrooms")
        testMock.getInventory.assert_called_with("mushrooms", 10)
        print testMock.mock_calls
        mockPatch.stop()

This routine also has two subtests. This time though, I have a patcher (testPatch) do the patching. In the first subtest (lines 15-29), I use a key/value sequence (mockArgs) to define the mock's method attributes. In the second one (lines 32-48), I define those same method attributes right after calling start(). Apart from the attribute values and assert conditions, testE_orderIncomplete() is structurally the same as testC_orderCheck().

To see the entire unit test setup, download the Python script.

Conclusion

Patching enables us to modify a class or object to suit a specific test. It also lets us create a temporary mock for a test routine.

This article demonstrates how patching can be tremendously useful in unit testing. We examined the four patch decorators from the Mock module, and learned how they work with class or object. We reviewed the MagicMock class and explored three patching techniques, using them in a typical unit test setup.

Recommended Reading

Martin Fowler. Mocks aren't Stubs

Michael Foord. Mock Documentation, Release 1.0.1 [PDF]

Michael Foord. Mocking, Patching, Stubbing: All that Stuff. Voidspace

Most Recent Call. Mock Recipes

My Adventures in Coding. Python — Getting Started with Python Mock


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


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