Python Testing: Mock Objects

Exclusive offer: get 50% off this eBook here
Python Testing: Beginner's Guide

Python Testing: Beginner's Guide — Save 50%

An easy and convenient approach to testing your powerful Python projects

£14.99    £7.50
by Daniel Arbuckle | December 2010 | Beginner's Guides

In this article by Daniel Arbuckle, author of Python Testing, we shall:

  • Examine the ideas of mock objects in general
  • Learn how to use Python Mocker
  • Learn how to mock the "self" parameter of a method

 

Python Testing: Beginner's Guide

Python Testing: Beginner's Guide

An easy and convenient approach to testing your powerful Python projects

  • Covers everything you need to test your code in Python
  • Easiest and enjoyable approach to learn Python testing
  • Write, execute, and understand the result of tests in the unit test framework
  • Packed with step-by-step examples and clear explanations
        Read more about this book      

(For more resources on Python, see here.)

So let's get on with it!

Installing Python Mocker

For the first time, we're using a tool that isn't included in the standard Python distribution. That means that we need to download and install it.

Time for action – installing Python Mocker

  • At the time of this writing, Python Mocker's home page is located at http://labix.org/mocker, while its downloads are hosted at https://launchpad.net/mocker/+download. Go ahead and download the newest version, and we'll see about installing it.
  • The first thing that needs to be done is to unzip the downloaded file. It's a .tar.bz2, which should just work for Unix, Linux, or OSX users. Windows users will need a third-party program (7-Zip works well: http://www.7-zip.org/) to uncompress the archive. Store the uncompressed file in some temporary location.
  • Once you have the files unzipped somewhere, go to that location via the command line. Now, to do this next step, you either need to be allowed to write files into your Python installation's site-packages directory (which you are, if you're the one who installed Python in the first place) or you need to be using Python version 2.6 or higher.
  • If you can write to site-packages, type
    $ python setup.py install
  • If you can't write to site-packages, but you're using Python 2.6 or higher, type
    $ python setup.py install --user

Sometimes, a tool called easy_install can simplify the installation process of Python modules and packages. If you want to give it a try, download and install setuptools from http://pypi.python.org/pypi/setuptools, according to the directions on that page, and then run the command easy_install mocker. Once that command is done, you should be ready to use Nose.

Once you have successfully run the installer, Python Mocker is ready for use.

The idea of a mock object

"Mock" in this sense means "imitation," and that's exactly what a mock object does. Mock objects imitate the real objects that make up your program, without actually being those objects or relying on them in any way.

Instead of doing whatever the real object would do, a mock object performs predefined simple operations that look like what the real object should do. That means its methods return appropriate values (which you told it to return) or raise appropriate exceptions (which you told it to raise). A mock object is like a mockingbird; imitating the calls of other birds without comprehending them.

We've already used one mock object in our earlier work when we replaced time.time with an object (in Python, functions are objects) that returned an increasing series of numbers. The mock object was like time.time, except that it always returned the same series of numbers, no matter when we ran our test or how fast the computer was that we ran it on. In other words, it decoupled our test from an external variable.

That's what mock objects are all about: decoupling tests from external variables. Sometimes those variables are things like the external time or processor speed, but usually the variables are the behavior of other units.

Python Mocker

The idea is pretty straightforward, but one look at that mock version of time.time shows that creating mock objects without using a toolkit of some sort can be a dense and annoying process, and can interfere with the readability of your tests. This is where Python Mocker (or any of several other mock object toolkits, depending on preference) comes in.

Time for action – exploring the basics of Mocker

We'll walk through some of the simplest—and most useful—features of Mocker. To do that, we'll write tests that describe a class representing a specific mathematical operation (multiplication) which can be applied to the values of arbitrary other mathematical operation objects. In other words, we'll work on the guts of a spreadsheet program (or something similar).

We're going to use Mocker to create mock objects to stand in place of the real operation objects.

  • Create up a text file to hold the tests, and add the following at the beginning (assuming that all the mathematical operations will be defined in a module called operations):
    >>> from mocker import Mocker
    >>> import operations
  • We've decided that every mathematical operation class should have a constructor accepting the objects representing the new object's operands. It should also have an evaluate function that accepts a dictionary of variable bindings as its parameter and returns a number as the result. We can write the tests for the constructor fairly easily, so we do that first (Note that we've included some explanation in the test file, which is always a good idea):
    We're going to test out the constructor for the multiply operation, first.
    Since all that the constructor has to do is record all of the operands,
    this is straightforward.


    >>> mocker = Mocker()
    >>> p1 = mocker.mock()
    >>> p2 = mocker.mock()
    >>> mocker.replay()
    >>> m = operations.multiply(p1, p2)
    >>> m.operands == (p1, p2)
    True
    >>> mocker.restore()
    >>> mocker.verify()
  • The tests for the evaluate method are somewhat more complicated, because there are several things we need to test. This is also where we start seeing the real advantages of Mocker:
    Now we're going to check the evaluate method for the multiply operation. 
    It should raise a ValueError if there are less than two operands, it should
    call the evaluate methods of all operations that are operands of the multiply,
    and of course it should return the correct value.

    >>> mocker = Mocker()
    >>> p1 = mocker.mock()
    >>> p1.evaluate({}) #doctest: +ELLIPSIS
    <mocker.Mock object at ...>
    >>> mocker.result(97.43)

    >>> mocker.replay()

    >>> m = operations.multiply(p1)
    >>> m.evaluate({})
    Traceback (most recent call last):
    ValueError: multiply without at least two operands is meaningless

    >>> mocker.restore()
    >>> mocker.verify()

    >>> mocker = Mocker()
    >>> p1 = mocker.mock()
    >>> p1.evaluate({}) #doctest: +ELLIPSIS
    <mocker.Mock object at ...>
    >>> mocker.result(97.43)
    >>> p2 = mocker.mock()
    >>> p2.evaluate({}) #doctest: +ELLIPSIS
    <mocker.Mock object at ...>
    >>> mocker.result(-16.25)

    >>> mocker.replay()

    >>> m = operations.multiply(p1, p2)
    >>> round(m.evaluate({}), 2)
    -1583.24

    >>> mocker.restore()
    >>> mocker.verify()
  • If we run the tests now, we get a list of failed tests. Most of them are due to Mocker being unable to import the operations module, but the bottom of the list should look like this:

  • Finally, we'll write some code in the operations module that passes these tests, producing the following:
    class multiply:
    def __init__(self, *operands):
    self.operands = operands
    def evaluate(self, bindings):
    vals = [x.evaluate(bindings) for x in self.operands]
    if len(vals) < 2:
    raise ValueError('multiply without at least two '
    'operands is meaningless')
    result = 1.0
    for val in vals:
    result *= val
    return result
  • Now when we run the tests, none of them should fail.

What just happened?

The difficulty in writing the tests for something like this comes(as it often does) from the need to decouple the multiplication class from all of the other mathematical operation classes, so that the results of the multiplication test only depend on whether multiplication works correctly.

We addressed this problem by using the Mocker framework for mock objects. The way Mocker works is that you first create an object representing the mocking context, by doing something such as mocker = Mocker(). The mocking context will help you create mock objects, and it will store information about how you expect them to be used. Additionally, it can help you temporarily replace library objects with mocks (like we've previously done with time.time) and restore the real objects to their places when you're done. We'll see more about doing that in a little while.

Once you have a mocking context, you create a mock object by calling its mock method, and then you demonstrate how you expect the mock objects to be used. The mocking context records your demonstration, so later on when you call its replay method it knows what usage to expect for each object and how it should respond. Your tests (which use the mock objects instead of the real objects that they imitate), go after the call to replay.

Finally, after test code has been run, you call the mocking context's restore method to undo any replacements of library objects, and then verify to check that the actual usage of the mocks was as expected.

Our first use of Mocker was straightforward. We tested our constructor, which is specified to be extremely simple. It's not supposed to do anything with its parameters, aside from store them away for later. Did we gain anything at all by using Mocker to create mock objects to use as the parameters, when the parameters aren't even supposed to do anything? In fact, we did. Since we didn't tell Mocker to expect any interactions with the mock objects, it will report nearly any usage of the parameters (storing them doesn't count, because storing them isn't actually interacting with them) as errors during the verify step. When we call mocker.verify(), Mocker looks back at how the parameters were really used and reports a failure if our constructor tried to perform some action on them. It's another way to embed our expectations into our tests.

We used Mocker twice more, except in those later uses we told Mocker to expect a call to an evaluate method on the mock objects (i.e. p1 and p2), and to expect an empty dictionary as the parameter to each of the mock objects' evaluate call. For each call we told it to expect, we also told it that its response should be to return a specific floating point number. Not coincidentally, that mimics the behavior of an operation object, and we can use the mocks in our tests of multiply.evaluate.

If multiply.evaluate hadn't called the evaluate methods of mock, or if it had called one of them more than once, our mocker.verify call would have alerted us to the problem. This ability to describe not just what should be called but how often each thing should be called is a very useful too that makes our descriptions of what we expect much more complete. When multiply.evaluate calls the evaluate method of mock, the values that get returned are the ones that we specified, so we know exactly what multiply.evaluate ought to do. We can test it thoroughly, and we can do it without involving any of the other units of our code. Try changing how multiply.evaluate works and see what mocker.verify says about it.

Mocking functions

Normal objects (that is to say, objects with methods and attributes created by instantiating a class) aren't the only things you can make mocks of. Functions are another kind of object that can be mocked, and it turns out to be pretty easy.

During your demonstration, if you want a mock object to represent a function, just call it. The mock object will recognize that you want it to behave like a function, and it will make a note of what parameters you passed it, so that it can compare them against what gets passed to it during the test.

For example, the following code creates a mock called func, which pretends to be a function that, when called once with the parameters 56 and hello, returns the number 11. The second part of the example uses the mock in a very simple test:

>>> from mocker import Mocker
>>> mocker = Mocker()
>>> func = mocker.mock()
>>> func(56, "hello") # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(11)
>>> mocker.replay()
>>> func(56, "hello")
11
>>> mocker.restore()
>>> mocker.verify()

Mocking containers

Containers are another category of somewhat special objects that can be mocked. Like functions, containers can be mocked by simply using a mock object as if it were a container during your example.

Mock objects are able to understand examples that involve the following container operations: looking up a member, setting a member, deleting a member, finding the length, and getting an iterator over the members. Depending on the version of Mocker, membership testing via the in operator may also be available.

In the following example, all of the above capabilities are demonstrated, but the in tests are disabled for compatibility with versions of Mocker that don't support them. Keep in mind that even though, after we call replay, the object called container looks like an actual container, it's not. It's just responding to stimuli we told it to expect, in the way we told it to respond. That's why, when our test asks for an iterator, it returns None instead. That's what we told it to do, and that's all it knows.

>>> from mocker import Mocker
>>> mocker = Mocker()
>>> container = mocker.mock()
>>> container['hi'] = 18
>>> container['hi'] # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(18)
>>> len(container)
0
>>> mocker.result(1)
>>> 'hi' in container # doctest: +SKIP
True
>>> mocker.result(True)
>>> iter(container) # doctest: +ELLIPSIS
<...>
>>> mocker.result(None)
>>> del container['hi']
>>> mocker.result(None)
>>> mocker.replay()
>>> container['hi'] = 18
>>> container['hi']
18
>>> len(container)
1
>>> 'hi' in container # doctest: +SKIP
True
>>> for key in container:
... print key
Traceback (most recent call last):
TypeError: iter() returned non-iterator of type 'NoneType'
>>> del container['hi']
>>> mocker.restore()
>>> mocker.verify()

Something to notice in the above example is that during the initial phase, a few of the demonstrations (for example, the call to len) did not return a mocker.Mock object, as we might have expected. For some operations, Python enforces that the result is of a particular type (for example, container lengths have to be integers), which forces Mocker to break its normal pattern. Instead of returning a generic mock object, it returns an object of the correct type, although the value of the returned object is meaningless. Fortunately, this only applies during the initial phase, when you're showing Mocker what to expect, and only in a few cases, so it's usually not a big deal. There are times when the returned mock objects are needed, though, so it's worth knowing about the exceptions.

 

Python Testing: Beginner's Guide An easy and convenient approach to testing your powerful Python projects
Published: January 2010
eBook Price: £14.99
Book Price: £24.99
See more
Select your format and quantity:
        Read more about this book      

(For more resources on Python, see here.)

Parameter matching

Sometimes, we would like our mocked functions and methods to accept a whole domain of parameters, instead of limiting itself to the accepting objects that compare equal to the parameters we specifically told it about. This can be useful for any number of reasons: perhaps the mock needs to accept an external variable as a parameter (the current time, or available disk space, for example), or maybe the mock example will be invoked multiple times, or maybe the parameters are simply not important to the definition of correct behavior.

We can tell a mock function to accept a domain of parameters by using the ANY, ARGS, KWARGS, IS, IN, CONTAINS, and MATCH special values, all of which are defined in the mocker module. These special values are passed to a mock object as function call parameters during its demonstration phase (before you call replay).

ANY

Passing ANY as a function parameter causes the object to accept any single object as its parameter in that position.

>>> from mocker import Mocker, ANY
>>> mocker = Mocker()
>>> func = mocker.mock()
>>> func(7, ANY) # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(5)
>>> mocker.replay()
>>> func(7, 'this could be anything')
5
>>> mocker.restore()
>>> mocker.verify()

ARGS

Passing ARGS as a function parameter causes the object to accept any number of positional arguments, as if it had been declared with *args in its parameter list.

>>> from mocker import Mocker, ARGS
>>> mocker = Mocker()
>>> func = mocker.mock()
>>> func(7, ARGS) # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(5)
>>> mocker.replay()
>>> func(7, 'this could be anything', 'so could this', 99.2)
5
>>> mocker.restore()
>>> mocker.verify()

KWARGS

Passing KWARGS as a function parameter causes the object to accept any number of keyword arguments, as if it had been declared with **kwargs in its parameter list.

>>> from mocker import Mocker, KWARGS
>>> mocker = Mocker()
>>> func = mocker.mock()
>>> func(7, KWARGS) # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(5)
>>> mocker.replay()
>>> func(7, a='this could be anything', b='so could this')
5
>>> mocker.restore()
>>> mocker.verify()

IS

Passing IS(some_object) is unusual, because instead of being an inexact parameter, it's more exact than the default. Mocker will normally accept any parameter that is == to the value passed during the initial phase, but if you use IS, it instead checks whether the parameter and some_object are in fact the exact same object, and only accepts the call if they are.

>>> from mocker import Mocker, IS
>>> mocker = Mocker()
>>> param = [1, 2, 3]
>>> func = mocker.mock()
>>> func(7, IS(param)) # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(5)
>>> mocker.replay()
>>> func(7, param) # func(7, [1, 2, 3]) would fail
5
>>> mocker.restore()
>>> mocker.verify()

IN

Passing IN(some_container) causes Mocker to accept any parameter that is contained in the container object called some_container.

>>> from mocker import Mocker, IN
>>> mocker = Mocker()
>>> func = mocker.mock()
>>> func(7, IN([45, 68, 19])) # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(5)
>>> func(7, IN([45, 68, 19])) # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(5)
>>> func(7, IN([45, 68, 19])) # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(5)
>>> mocker.replay()
>>> func(7, 19)
5
>>> func(7, 19)
5
>>> func(7, 45)
5
>>> mocker.restore()
>>> mocker.verify()

CONTAINS

Passing CONTAINS(some_object) causes Mocker to accept any parameter for which some_object in parameter is True.

>>> from mocker import Mocker, CONTAINS
>>> mocker = Mocker()
>>> func = mocker.mock()
>>> func(7, CONTAINS(45)) # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(5)
>>> mocker.replay()
>>> func(7, [12, 31, 45, 18])
5
>>> mocker.restore()
>>> mocker.verify()

MATCH

Finally, if none of the above lets you describe the conditions under which you want Mocker to accept a parameter as matching its expectation, you can pass MATCH(test_function). The test_function should be a function with one parameter, which will be passed the received parameter when the mocked function gets called. If the test_function returns True, the parameter is accepted.

>>> from mocker import Mocker, MATCH
>>> def is_odd(val):
... return val % 2 == 1
>>> mocker = Mocker()
>>> func = mocker.mock()
>>> func(7, MATCH(is_odd)) # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(5)
>>> mocker.replay()
>>> func(7, 1001)
5
>>> mocker.restore()
>>> mocker.verify()

Mocking complex expressions

It would be nice to be able to combine the various operations that Mocker's mock objects support. Simple attribute accesses, container member accesses and method calls make up the majority of object interactions, but they are commonly used in combinations, like container[index].attribute.method(). We could write a demonstration of something equivalent to this out, step-by-step, using the things we already know about Mocker's mock objects, but it would be nice to be able to just write the example as we expect it to be in the actual code.

Fortunately, we can usually do exactly that. Throughout the previous examples in this chapter, you've been seeing expressions that return <mocker.Mock object at ...>. Those return values are mock objects, just like the ones you create by calling Mocker.mock, and they can be used in the same ways. That means that as long as part of a complex expression returns a mock object during the demonstration, you can continue chaining more parts of the complex expression onto it. With something like container[index].attribute.method(), container[index] returns a mock object, attribute access on that object returns another mock object, and we call a method on that object. The method call also returns a mock object, but we don't need to do anything with it in order to correctly demonstrate our expectations.

Mocker remembers our demonstration of use, no matter how complex it is or how deeply we drill down into nested objects. Later after we call replay, it checks that the usage is as we described it, even for very complicated usage patterns.

Returning iterators

So far, we've been calling Mocker.result to tell Mocker that the result of evaluating a particular example expression should be some specific value. That's great for simulating most expressions, and it covers the common usage of functions and methods as well, but it doesn't really do the trick for simulating a generator, or other function that returns an iterator. To handle that, we call Mocker.generate instead of Mocker.result, like so:

>>> from mocker import Mocker
>>> from itertools import islice
>>> mocker = Mocker()
>>> generator = mocker.mock()
>>> generator(12) # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.generate([16, 31, 24, 'hike'])
>>> mocker.replay()
>>> tuple(islice(generator(12), 1, 2))
(31,)
>>> mocker.restore()
>>> mocker.verify()

Raising exceptions

Some expressions raise an exception instead of returning a result, so we need to be able to make our mock objects do the same. Fortunately, it's not difficult: you call Mocker.throw to tell Mocker that the correct response to an expected expression is to raise a particular exception.

>>> from mocker import Mocker
>>> mocker = Mocker()
>>> obj = mocker.mock()
>>> obj.thingy # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.throw(AttributeError('thingy does not exist'))
>>> mocker.replay()
>>> obj.thingy
Traceback (most recent call last):
AttributeError: thingy does not exist
>>> mocker.restore()
>>> mocker.verify()

Calling functions via a mock

Sometimes a function that we're mocking has side-effects that are important to our tests. Mocker handles these situations by allowing you to specify one or more functions that should be called, when a particular expression occurs. These functions can either be existing functions that are pulled from somewhere in your codebase, or they can be special functions that you've embedded in your test specifically to produce the desired side effects.

There is one restriction on which functions can be called as a result of interacting with one of the mock objects of Mocker: such a function must not require any parameters. This isn't as big a restriction as you might think, because you know exactly which parameters should be passed to the called functions, and so you can write a small wrapper function that just calls the target function with those parameters. This is demonstrated in the next example.

The Python lambda keyword is a mechanism for wrapping a single expression up as a function. When the function gets called, the expression is evaluated, and whatever the expression evaluated to is returned from the function. The uses of lambda are many and varied, but using it to create minor wrappers around calls to other functions is a common one.

Calling functions in this way isn't exclusive with having the mocked function return a result. In the following example, the mocked function makes two function calls and returns the number 5.

>>> from mocker import Mocker
>>> from sys import stdout
>>> mocker = Mocker()
>>> obj = mocker.mock()
>>> obj.method() # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.call((lambda: stdout.write('hi')))
>>> mocker.call((lambda: stdout.write('yo\n')))
>>> mocker.result(5)
>>> mocker.replay()
>>> obj.method()
hiyo
5
>>> mocker.restore()
>>> mocker.verify()

Specifying that an expectation should occur multiple times

As you may have noticed in some of the preceding examples, sometimes telling Mocker what to expect can get repetitive. The example of the IN parameter matcher show this well: We did a lot of repetitive work telling Mocker that we expected three calls to the func function. That makes the test long (which reduces its readability) and it violates the DRY (Don't Repeat Yourself) principle of programming, making it harder to modify the test later on. Besides which, it's annoying to write all those duplicate expectations.

To solve this problem, Mocker allows us to specify the number of times that an expectation ought to occur during the execution of the test. We do this by calling Mocker.count to specify the expected number of repetitions. To see the simplest way to do that, let's re-write the IN example, so that we don't have to keep repeating ourselves:

>>> from mocker import Mocker, IN
>>> mocker = Mocker()
>>> func = mocker.mock()
>>> func(7, IN([45, 68, 19])) # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(5)
>>> mocker.count(3)
>>> mocker.replay()
>>> func(7, 19)
5
>>> func(7, 19)
5
>>> func(7, 45)
5
>>> mocker.restore()
>>> mocker.verify()

Notice how parameter matching works well with specifying a count, letting us compress several different calls to func into a single expectation, even though they have different parameters. By using these two features in conjunction, the expectations of a mock can often be shortened significantly, removing redundant information. Keep in mind though, that you don't want to remove important information from a test; if it mattered that the first call to func had 19 as its parameter, or that the calls came in a particular order, compressing the expectation this way would lose that information, which would compromise the test.

In the above example, we specified a precise number of times to expect the call to func to repeat, but count is more flexible than that. By giving it two parameters, count can be told to expect any number of repetitions between a minimum and a maximum number. As long as the actual number of repetitions during the test is at least as many as the minimum number, and no more than the maximum number, Mocker will accept it as correct usage.

>>> from mocker import Mocker, IN
>>> mocker = Mocker()
>>> func = mocker.mock()
>>> func(7, IN([45, 68, 19])) # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(5)
>>> mocker.count(1, 3)
>>> mocker.replay()
>>> func(7, 19)
5
>>> func(7, 45)
5
>>> func(7, 19)
5
>>> mocker.restore()
>>> mocker.verify()

Finally, it's possible to specify that an expectation is to be repeated at least a certain number of times, but with no maximum number of repetitions. As long as the expectation is met at least as many times as specified, Mocker considers its usage to have been correct. To do this, we pass None as the maximum parameter when we call count.

>>> from mocker import Mocker, IN
>>> mocker = Mocker()
>>> func = mocker.mock()
>>> func(7, IN([45, 68, 19])) # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(5)
>>> mocker.count(1, None)
>>> mocker.replay()
>>> [func(7, 19) for x in range(50)] == [5] * 50
True
>>> mocker.restore()
>>> mocker.verify()

That last example uses a couple of esoteric Python features. On the left side of the == is a "list comprehension," which is a compact way of constructing a list as a transformation of another iterable. On the right is list multiplication, which creates a new list containing the members of the old list repeated a number of times—in this case, the list contains 50 repetitions of the value 5.

Replacing library objects with mocks

Several times, we've seen a need to replace something outside of our own code with a mock object: for example, time.time needed to be replaced with something that produced predictable results, in order for the tests on our PID controller to be meaningful.

Mocker provides us with a tool to address this common need, and it's quite simple to use. Mocker's mocking contexts contain a method called replace which behaves pretty much like mock from our point of view, but which is able to completely replace an existing object with a mock object, no matter what module (or modules) it exists in, or when it was imported. Even better, when we call restore the mock goes away, and original object is returned to its rightful place.

This gives us an easy way to isolate our tests even from library code that we couldn't normally control, and to do it without leaving any trace after we're done.

To illustrate replace, we're going to temporarily replace time.time with a mock. We've done this before—in our PID tests—in an ad hoc manner. It made our tests ugly and difficult to read. It also only replaced the name time.time with our mock: if we'd done from time import time in our PID code, the replacement wouldn't have caught it unless the replacement was done before we imported PID. Mocker will handle such complex replacements correctly, no matter when the imports occur or what form they take, with no extra effort on our part.

>>> from time import time
>>> from mocker import Mocker
>>> mocker = Mocker()
>>> mock_time = mocker.replace('time.time')
>>> mock_time() # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(1.3)
>>> mock_time() # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(2.7)
>>> mock_time() # doctest: +ELLIPSIS
<mocker.Mock object at ...>
>>> mocker.result(3.12)
>>> mocker.replay()
>>> '%1.3g' % time()
'1.3'
>>> '%1.3g' % time()
'2.7'
>>> '%1.3g' % time()
'3.12'
>>> mocker.restore()
>>> mocker.verify()

Notice that we imported time before we replace it with a mock, and yet when we actually used it, it turned out to be the mock we were using. After the call to restore, if we'd called time again, it would have been the real time function again.

Why did we use string formatting on the output from time? We did this because floating point numbers are imprecise, meaning that the number we entered as 3.12, for example, might be represented in the system as 3.1200000000000001 or some other value that is very close to, but not precisely, 3.12. The exact value used can vary from system to system, so comparing against a float makes your tests less portable. Our string formatting rounded the number to just the relevant digits.

Mocking self

When a method of an object is called, it's first parameter is a reference to the object that contains the method. We'd like to be able to replace it with a mock, because that's the only way to truly separate each method, so that each can be tested as an individual unit. If we can't mock self, the methods will tend to interfere with each other's tests by interacting via their containing object.

The stumbling block in all this is that the self object isn't passed explicitly by the caller when a method gets called: Python already knows which object the method is bound to, and fills it in automatically. How can we substitute a mock for a parameter that doesn't come from us?

We can solve this problem by finding the function that we're testing in its class and invoking it directly, rather than invoking it as a method bound to an object. That way, we can pass all of the parameters, including the first one, without the interpreter performing any of its magic.

Time for action – passing a mock object as self

  • Remember the testable class that we used, among other things, to demonstrate how it can be difficult to separate methods so we can deal with them as units?
    class testable:
    def method1(self, number):
    number += 4
    number **= 0.5
    number *= 7
    return number
    def method2(self, number):
    return ((number * 2) ** 1.27) * 0.3
    def method3(self, number):
    return self.method1(number) + self.method2(number)
    def method4(self):
    return self.method3(id(self))
  • We're going to write a unit test for method3. Like all unit tests, it needs to not involve any code from any other unit, which in this case means that self.method1 and self.method2 need to be mock objects. The best way to achieve that is to have self itself be a mock object, so that's what we're going to do. The first step is to create a mock object that expects the interactions that method3 ought to perform:
    >>> from testable import testable
    >>> from mocker import Mocker
    >>> mocker = Mocker()
    >>> target = mocker.mock()
    >>> target.method1(12) # doctest: +ELLIPSIS
    <mocker.Mock object at ...>
    >>> mocker.result(5)
    >>> target.method2(12) # doctest: +ELLIPSIS
    <mocker.Mock object at ...>
    >>> mocker.result(7)
  • method3 is supposed to call method1 and method2, and the mock we just created expects to see calls to method1 and method2. So far, so good, so what's the trick to getting this mock object to be self for a call to method3? Here's the rest of the test:
    >>> mocker.replay()
    >>> testable.method3.im_func(target, 12)
    12

What just happened?

We went to the testable class and looked up its method3 member, which is something called an "unbound method object." Once we had an unbound method object, we looked inside of it for its im_func attribute, which is simply a function, without any of the razzmatazz associated with methods. Once we had a normal function in hand, it was easy to call it, and pass our mock object as its first parameter.

Python version 3.0 made this easier, by getting rid of unbound method objects in favor of just storing the function object directly in the class. This means that if you're using Python 3.0 or higher, you can just call testable.method3(target, 12).

Summary

We learned a lot in this article about mocking, and about the Python Mocker. We focused on the assorted features that Mocker provides to help you keep units separate from each other.

Specifically, we covered what mock objects are, and what they're for, how to use Python Mocker to make mocking easier, lots of ways to customize Mocker's behavior to suit your needs, and how to substitute a mock object for a method's self parameter.


Further resources on this subject:


Python Testing: Beginner's Guide An easy and convenient approach to testing your powerful Python projects
Published: January 2010
eBook Price: £14.99
Book Price: £24.99
See more
Select your format and quantity:

About the Author :


Daniel Arbuckle

Daniel Arbuckle holds a Ph.D. in Computer Science from the University of Southern California. While at USC, he performed original research in the Interaction Lab (part of the Center for Robotics and Embedded Systems) and the Laboratory for Molecular Robotics (now part of the Nanotechnology Research Laboratory). His work has been published in peer-reviewed journals and in the proceedings of international conferences.

Books From Packt


Python 2.6 Graphics Cookbook
Python 2.6 Graphics Cookbook

Python 2.6 Text Processing Beginners Guide
Python 2.6 Text Processing Beginners Guide

wxPython 2.8 Application Development Cookbook
wxPython 2.8 Application Development Cookbook

MySQL for Python
MySQL for Python

Expert Python Programming
Expert Python Programming

Python 3 Object Oriented Programming
Python 3 Object Oriented Programming

Spring Python 1.1
Spring Python 1.1

Python Multimedia
Python Multimedia


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software