Python Mock Gotchas

Published on 16 November 2012, updated on 16 November 2012, Comments

Mock is a Python mocking and testing library. It has become a de facto standard and is now included in the Python standard library.

Lately I’ve had the chance to work on a fairly large code base built upon a service-oriented architecture. In order to test each service in isolation, we make extensive use of Mock to simulate services that the code under test depends on.

During this process, we noticed that each member our team seemed to be hitting the same pitfalls, which I’m going to present in this article in the hope that it might help other developers.

Patching in the wrong place

The first thing that puzzles people is when the patch method seem to have no effect.

Let’s consider a very simple example where you have some kind of domain model object that uses a data source module to get data and return it to the client. In a real world situation, the data source module would query a database or an external service. For the sake of simplicity, here the data source just returns hard-coded values:

# data_source.py
def get_name():
    return "Alice"

The Person class exposes a method that fetches data from the data source:

# person.py
from data_source import get_name

class Person(object):
    def name(self):
        return get_name()

One might then start to write a test case that looks like this:

# test_person.py
from mock import patch
from person import Person

# mock the get_name function
@patch('data_source.get_name') # This won't work as expected!
def test_name(mock_get_name):
    # set a return value for our mock object
    mock_get_name.return_value = "Bob" 
    person = Person()
    name = person.name()
    assert name == "Bob"

Unfortunately, the example above doesn’t work as expected. If you run it with a testing tool such as nose, you’ll get a result similar to:

$ nosetests test_person.py
F
======================================================================
FAIL: test_person.test_name
----------------------------------------------------------------------
[...]
    return func(*args, **keywargs)
  File "/home/al/essais/python/test_person.py", line 13, in test_name
    assert name == "Bob"
AssertionError

----------------------------------------------------------------------
Ran 1 test in 0.002s

FAILED (failures=1)

The reason why it doesn’t work is explained in the documentation in a section called Where to patch. Now go and read it if you haven’t already, I’ll wait for you right here.

The problem in the example above is that we’re changing where the name get_name points to within the data_source module. However the code we’re testing doesn’t refer to get_name within the data_source module, it refers to that name within the person module.

Our patching would have worked correctly if the code under test had referred to get_name within the data_source module like this:

# person.py
import data_source

class Person(object):
    def name(self):
        return data_source.get_name()

If we want to leave our application code unchanged, we’ll have to change the way we use Mock within our test so that it modifies where get_name points to within the person module:

from mock import patch
from person import Person

@patch('person.get_name')
def test_name(mock_get_name):
    mock_get_name.return_value = "Bob"
    person = Person()
    name = person.name()
    assert name == "Bob"

Forgetting to patch the return_value

Another common issue is patching an object instead of patching the return value of that object. It is particularly easy to get trapped into this when patching objects deep down inside a chain of class instantiations and method calls.

Let’s say Person has a relationship with Pet:

class Person(object):
    def __init__(self):
        self.pet = Pet()
    [... other methods ...]

class Pet(object):
    def noise(self):
        return "Woof"

It might look natural to write a test like this:

@patch('person.Pet')
def test_dog_noise(mock_pet):
    mock_pet.noise.return_value = "Meoow"
    person = Person()
    assert person.pet.noise() == "Meoow"

This won’t work because a pet object is the result of instantiating the Pet class, which in Python means calling the class. We must therefore access the noise method via the return_value of the mocked Pet class:

@patch('person.Pet')
def test_dog_noise(mock_pet):
    # Here we need an extra `return_value` attribute in order to access the
    # instance of the class
    mock_pet.return_value.noise.return_value = "Meoow"
    person = Person()
    assert person.pet.noise() == "Meoow"

Patching decorators

Patching decorators might also lead to some surprising behaviors. Let’s say we have a very simple decorator that just logs the result of decorated functions:

# decorators.py
def noise_logger(func):
    def wrapped(self):
        result = func(self)
        # In a real-world scenario, the decorator would access an external
        # resource which we don't want our tests to depend on, such as a
        # caching service.
        print "Pet made noise: ", result
        return result
    return wrapped

Our domain model makes use of that decorator:

# person.py
from decorators import noise_logger

class Person(object):
    def __init__(self):
        self.pet = Pet()

class Pet(object):
    @noise_logger
    def noise(self):
        return "Woof"

Now we’d like to patch our decorator to make sure it doesn’t call any external service we want to isolate from. Intuitively, we might write a test like this:

from mock import patch
from person import Person

@patch('person.noise_logger', lambda x: x)
def test_decorator():
    person = Person()
    assert person.pet.noise() == "Woof"

Unfortunately this won’t work at all because by the time we patch our decorator, it has already been applied and our noise method has already been wrapped. Here is what happens if we run our test with the -s switch, which tells nose to print stdout output:

$ nosetests -s test_person.py
Pet made noise:  Woof
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

This is because the decorator has been applied when the class definition was loaded, as soon as we imported the person module. Remember that instructions within a class definition are typically interpreted when we load the module containing the class. Consider this:

def useless_decorator(func):
    print "Hi, I'm a decorator that does nothing."
    return func

class Foo(object):
    print "Entering Foo class definition"

    @useless_decorator
    def bar(self):
        return 42

print "OK, we're done with that class definition."

If you execute this code with your Python interpreter, you should get this output:

Entering Foo class definition
Hi, I'm a decorator that does nothing.
OK, we're done with that class definition.

I hope this silly example convinces you that decorators are applied when the class is created, which happens straight away when we load the module containing the class.

To deal with this you first have to make sure that your decorator is defined in a module separate from the class, otherwise you’ll never get a chance to replace it with a mock before the class definition calls it. Then you need to write your test code so that it patches the decorator before it gets applied to the methods of your class:

from mock import patch
patch('decorators.noise_logger', lambda x: x).start()
from person import Person

def test_decorator():
    person = Person()
    assert person.pet.noise() == "Woof"

Now if you run nosetests with -s, you shouldn’t see any message from the decorator because it’s been replaced by a lambda that does nothing.

Need help with your Python project? Learn more about my profile and contact me!
blog comments powered by Disqus