Unraveling the Mystery of Python Unit Test Mocking

In this post I present a tutorial on how I unit test python code and some good examples of the power of the mock library.
July 10th, 2014  by Blaine Garrett

In this post I present a tutorial on how I unit test python code and some good examples of the power of the mock library.

On a my recent visit to Amsterdam, I popped into the EU office and spent a day teaching unit testing and proper mocking techniques. It was fun to share the wisdom, however, the devs had few resources available to them and didn't quite understand the nuances of more advanced unit testing techniques. Since back, I have found myself teaching the techniques to a friend. I figured it was appropriate to put together a blog post sharing what I know with others.

Before we begin:

  • Be sure to have a basic understanding of unit tests and why they are important. It is not the goal of this article to argue why unit tests are needed. More so it is to argue that you should use the mock library to test every aspect of your code in an isolated manner
  • Install the mock library. The easiest way to install it, is via pip install mock on the command line.
  • Install the test runner Nose. This will make running tests a breeze and save a lot of boiler plate code. The easiest way to install it, is via pip install nose on the command line.
  • Be able to write and run python code, obviously.

Example Code and Running Nose Tests

Throughout this post, I am putting all of my code in a single file called examples.py. Normally, you would have a separate directory for test code, but for ease, I am using a single file. To run tests via the command line, be sure to be in the same directory as your example.py and run:

nosetests -vs --tests=examples.py


A Simple Unit Test Example

Here is a very simple python method:

[source="python"]
def square(n):
  	""" A simple method to take in a number n and return the square of the number """
  	if not isinstance(n, (int, float)):
  	    raise RuntimeError('Argument must be an int or float.')
	return n * n
[/source]

A simple set of Tests:

[source="python"]
class SquareTestCase(unittest.TestCase):
    """ A set of tests surrounding our square method """

    def test_positive_int(self):
        self.assertEqual(square(2), 4)

    def test_positive_float(self):
        self.assertEqual(square(.5), .25)
            
    def test_negative_int(self):
        self.assertEqual(square(-9), 81)

    def test_errors(self):
        self.assertRaises(RuntimeError, square, 'NaN')
[/source]

If you run nose tests now, you should get something like:

$ nosetests -vs --tests=examples.py 
test_negative_int (examples.SquareTestCase) ... ok
test_none_int (examples.SquareTestCase) ... ok
test_positive_float (examples.SquareTestCase) ... ok
test_positive_int (examples.SquareTestCase) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK

Important Best Practices to Note:

Even though our square method is only a few lines, we have 4 tests. This may be overkill for this simple of method, but it is important to note what is going on.

  • Every explicit case is tested including float handling and type checking (re: test_positive_float and test_errors)
  • Even implicit cases are tests (re: test_negative_int)
  • Our test class only tests the the code we wrote (this will make more sense later)
  • Our test class only encompasses tests for a single method - this is just good practice.

Basic Mocking Concepts - Peering into Black Boxes

The python mock library contains 2 main components

mock.Mock and mock.MagicMock

Multipurpose classes that can emulate objects, methods, etc. Properties, methods, etc can all be overridden. These classes maintains a listing of if the "thing" mocked was called, how many times it was called, and with what arguments. You can also have the "thing" throw Exceptions and other side effects. The key difference between Mock and Magic mock is that the later will not throw an AttributeError if a property does not exist on it. Rather it will simply return None.

mock.patch

A method that takes in string path to a module, class, method, etc and replaces it with an instance of the above Mock class. This is good for when your code calls other code that:

  • is already tested in a concise manner (such as our square method above)
  • expensive to call such as database operations or urlfetchs
  • yields unwanted side effects such as sending of emails or RPCs to external apis
  • calls system imports such as python's datetime that you want to have more fine grain control of for testing purposes

 

An Example where Mocking is Useful
Consider the following code that generates a number representing the season based on todays date.

[source="python"]
import datetime as dt # Put this at the top of the file...

def get_season():
    """
    Method to calculate the season of the current day (1=Spring, 2=Summer, 3=Fall, 4=Winter)
    """
    d = dt.date.today()
    month = d.month
    
    if month in [3,4,5]: return 1
    if month in [6,7,8]: return 2
    if month in [9,10, 11]: return 3
    if month in [12, 1, 2]: return 4
    else:
        raise RuntimeError('Invalid value for month %s' % month)
[/source]

Lets think about how you would test this without mocking for a second. Since the method uses today's date, the value of the method depends on when the test is run. So for example, if you ran the test in January, you would get 1. Running it 3 months later would give you a different value. This leads itself to unpredictability. You could test that get_season returns something. You could test that it is an integer. You could test that it is also between 1 and 12 inclusive. However, we have four logic branches here each with 3 conditions (i.e. the set of month numbers). That said, without mocking, we cannot test the specific cases nor do we have a ton of confidence in the predictability of the test.

Mock the datetime module to control what "today" is

To more thoroughly test our method, we want to control what the value of "today" is. We will want to create a fake python date object and then patch the datetime python module so we can control what it returns. Note: We patch the import of the datetime module not the module itself. Many low level python modules, such as datetime, cannot be patched and thus the import of these modules needs to be what is patched.

[source="python"]
class GetSeasonTestCases(unittest.TestCase):
    """ Tests Surrounding get_season method """
    
    @patch('examples.dt')
    def test_base(self, mocked_datetime):
        # Setup Mocking of datetime.date.today and create mocked date object
        mocked_today = Mock()
        mocked_datetime.date.today.return_value = mocked_today

        # Run our tests
        mocked_today.month = 1
        self.assertEqual(get_season(), 4)

        mocked_today.month = 2
        self.assertEqual(get_season(), 4)

        mocked_today.month = 3
        self.assertEqual(get_season(), 1)

        mocked_today.month = 4
        self.assertEqual(get_season(), 1)

        mocked_today.month = 5
        self.assertEqual(get_season(), 1)

        mocked_today.month = 6
        self.assertEqual(get_season(), 2)

        mocked_today.month = 7
        self.assertEqual(get_season(), 2)

        mocked_today.month = 8
        self.assertEqual(get_season(), 2)

        mocked_today.month = 9
        self.assertEqual(get_season(), 3)

        mocked_today.month = 10
        self.assertEqual(get_season(), 3)

        mocked_today.month = 11
        self.assertEqual(get_season(), 3)

        mocked_today.month = 12
        self.assertEqual(get_season(), 4)
        
        # Check to ensure that we called our mocked .today() method
        self.assertEqual(mocked_datetime.date.today.call_count, 12)
[/source]

As you can see, through the use of mocking we were able to test how our code handles regardless of when the test was run. Some key things to note:

  • As described above, we patch the import of the python datetime module, not the module itself
  • We used mock.patch as a decorator to our test method. As such, the newly patched datetime module is passed as the second argument to the test_ method and will be of type mock.MagicMock.
  • The date property of our mocked datetime module is also of type MagicMock as is the today() method. As such, we can specify the return_value of today() to be what we want it to be - i.e. the mocked_today​ object we created.
  • Normally, the month property on a python date object is immutable and cannot be assigned. However, since the mocked_today object is a Mock, we can update it as we like.
  • As previously mentioned, Mock objects keep track of how many times they are called and with what arguments. In the test above, the last thing we check is that our mocked today() method was called the expected number of times (i.e. once for each call to get_season()).

At this point, it is important to note that we have treated the python datetime libary as a black box of sorts. We make calls to it and we expect things back, but we don't actually care what it is doing under the hood. For all we know, the python datetime libary makes all sorts of api calls to the hardware that are cpu expensive, dependent on OS, or invoke some sort of black magic. We also assume that the developers of the library have written their own unit tests for it and as such, there is no need to actually test their code. As such, using mocks, we treat it like a black box and assume it will work. This might seem silly for something as simple as datetime, but it is the mindset you should get in when testing your code.

 

Next up, we actually peer into the black box a bit... (2nd post coming soon)