What and Why
PyMock is for developers who want to test their code at the unit level. It allows
you to imitate the behavior of methods in limited situations without relying on
their implementation. This can be done by hand, but creating mocking classes
from scratch takes effort and time. PyMock makes this process easier.
Downloading and Installing
PyMock is available as a python source distribution. Download the file
pymock-1.0.5-py2.5.egg.
Put it into your python site directory, and then require it as needed.
PyMock is also availabe through easy_install. If you've already installed
setuptools the just run easy_install PyMock.
Previous releases: pymock-1.0.4.tar.gz,
pymock-1.0.tar.gz
What is New in 1.0.5
- A new DSL like front end.
- Nose support.
- Bug fixes.
- The unit tests match the code.
- Distributed as an egg.
What is New in 1.0.4
This release is a bug fix. There were a number of egregious bugs relating
to the unittest.TestCase that have been fixed. The behavior now matches
that documented in the examples.
More Details
PyMock is based on the java easymock package http://www.easymock.org.
It uses a recording and replay model rather than using a specification language.
Easymock lives up to it's name compared to other mocking packages. PyMock
takes advantage of python's dynamic nature for futher improvements.
In addition to allowing you to mock out simple function calls PyMock allows you
to mock out properties, generators, and raw functions. It can also temporarily
override all of these entities within existing classes and objects. (For instance
you can mock out os.listdir() for a single test.
PyMock does have several weakeness. You can't specify dynamic
parameter value checks, nor can you verify ordering between calls.
Be warned of several things. PyMock may not work if you're doing deep black
magic with metaclasses. You're likely to run into its machinery. If you check
object types explicitly you're likely to it's machinery too. In these cases you'll
need to fall back to more traditional means of testing.
How To, Tutorial, and Recipies
There are two ways to use the framework. They are standalone and as a
unittest.TestCase. For standalone you need to create a controller. As a
test case the controller is implicitly created.
Standalone:
from pymock import *
c = Controller()
m = c.mock()
From within a unittest:
from pymock import *
class MyTestCase(PyMockTestCase):
def testMethod(self):
m = self.mock()
I'll assume for the rest of the examples that we're using PyMockUnitTestCase. PyMock
uses a record and playback model. The controller starts in record mode, and you have to
tell it that you're changing to playback mode. Once you've played back all the methods
you can verify that this has happened as expected by calling the controller's verify method.
This example records, replays, and verify that a method was called.
def testMethod(self):
m = self.mock()
m.function()
self.replay()
m.function()
self.verify()
But function calls aren't particularly useful unless you can return a value.
This example shows a value being returned.
def testMethod(self):
m = self.mock()
m.function(5)
self.setReturn(6)
self.replay()
self.failUnless(m.function(5) == 6)
self.verify()
You can condense the calls a little bit with expectAndReturn(self, x, y).
def testMethod(self):
m = self.mock()
self.expectAndReturn(m.function(5), 6)
self.replay()
self.failUnless(m.function(5) == 6)
self.verify()
Exceptions can be thrown in a similar manner.
def testMethod(self):
m = self.mock()
m.function(5)
self.raiseException(Exception())
self.replay()
try:
m.function(5)
self.fail()
except Exception, e:
pass
self.verify()
Exceptions can be condensed too.
def testMethod(self):
m = self.mock()
self.expectAndRaise(m.function(5), Exception())
self.replay()
try:
m.function(5)
self.fail()
except Exception, e:
pass
self.verify()
Getting properties works in pretty much exactly the same way
as making function calls. Throwing exceptions works the same
way too.
def testMethod(self):
m = self.mock()
m.g
self.setReturn(7)
self.replay()
self.failUnless(m.g == 7)
self.verify()
Setting works too, but setReturn doesn't make any sense in
this context. However you can use setException to raise
exceptions, but since property setting is an a statement
and not an expression you're limited to using setReturn. The
expectAndRaise method just won't work for this case.
def testMethod(self):
m = self.mock()
m.g = 8
self.replay()
m.g = 8
self.verify()
Until you call setReturn on a function or a property get you are
in possession of another mock object. You can construct references
to chains of mock objects simply by making the calls on that object.
def testMethod(self):
m = self.mock()
x = m.f(8, 9)
x.g
x.g.h(7)
self.replay()
x = m.f(8, 9)
x.g
x.g.h(7)
self.verify()
You can turn any mock function call into a mock generator using
the generator method.
def testMethod(self):
m = self.mock()
self.generator(m.f(), [1, 2, 3])
g = m.f()
self.replay()
self.failUnless(g.next() == 1)
self.failUnless(g.next() == 2)
self.failUnless(g.next() == 3)
self.failUnlessRaises(StopIteration, g.next)
You can tell generator to terminate any sequence with an arbitrary
exception.
def testMethod(self):
m = self.mock()
self.generator(m.f(), [1, 2], Exception())
self.replay()
g = m.f()
self.failUnless(g.next() == 1)
self.failUnless(g.next() == 2)
self.failUnlessRaises(Exception, g.next)
The mocked __iter__() method is different from others. It is
automatically turned into a generator. You can set its return
values by using the generator call or by repeatedly calling
setReturn and, if wanted, by finally calling setException.
def testMethod(self):
m = self.mock()
self.__iter__()
self.setReturn(1)
self.setReturn(2)
self.replay()
g = m.f()
self.failUnless(g.next() == 1)
self.failUnless(g.next() == 2)
self.failUnlessRaises(StopIteration, g.next)
So far every mocked call has been used exactly once. Any more
or any less results in an error when run or verified. This need
not be the case.
There are a numer of calls to alter the playback behavior. They
are setCount(x), atLeast(x), zeroOrMore(), and oneOrMore(). These
calls are used in a manner analogous to setReturn or setException.
The meanings are straight forward. setCount(x) says that the
preceeding call should be replayed precisely x times. atLeast(x) says
that the preceeding call should be replayed at least x times, but may
be replayed more. zeroOrMore() says the preceeding call can be
replayed any number of times, and finally oneOrMore() says the
preceeding call must by replayed at least once, but can be replayed
more often.
def testMethod(self):
m = mock()
expectAndReturn(m.f(), 2)
self.setCount(2)
expectAndReturn(m.f(), 3)
self.oneOrMore()
self.replay()
self.failUnless(m.f() == 2)
self.failUnless(m.f() == 2)
for x in range(0, 200):
self.failUnless(m.f() == 3)
self.verify()
There is one complication arising from these. atLeast, zeroOrMore,
and oneOrMore specify an indefinite and potentially infinite number
of playbacks. Once you set one of these specifiers you can't record
the preceeding call any more. You'll receive an error if you try to do
so.
Now comes the fun stuff. This is what makes PyMock different from
easymock or other java mocking systems. You're not limited to overriding
mock objects. You can mock out individual methods and fields in any
sort of object. When your code completes the mocked method, property,
or function vanishes.
Here's how you'd mock out calls to os.listdir.
def testMethod(self):
self.override(os, 'listdir') # object, method name
expectAndReturn(os.listdir(), ['one', 'two'])
self.replay()
self.failUnless(os.listdir() == ['one', 'two'])
self.verify()
Properties are a little different. They need a special declaration
since they're handled in a very different way. (Hmmmm... I may
have figured out an obvious way around that, but I want to get
this out the door first.) Here's how you'd mock out calls to
a property. Note that unlike other calls, all the calls to an overridden
property must be played back in order.
def testMethod(self):
self.overrideProperty(os, 'separator')
os.separator = '\\'
expectAndReturn(os.separator, '\\')
del os.separator
self.replay()
os.separator = '\\'
self.failUnless(os.separator == '\\')
del os.separator
self.verify()
A final note. If you're using PyMockTestCase then you
must ensure the inherited setUp and tearDown methods
are called. An example of code that does this follows.
class TestSetUpTeardownDemonstration(PyMockTestCase):
def setUp(self):
super(TestSetUpTearDownDemonstration, self).setUp()
... do local setup ...
def tearDown(self):
super(TestSetUpTearDownDemonstration, self).tearDown()
... do local tear down ...
def testMethod(self):
... do test ...
An Example Using Controllers vs. Using a Test Case
You can use the controllers directly or you can use the
ready made test case object. Both are shown below, along
with the code to be tested.
class X(object):
def write_string_to_file(self, f, str):
f.write(str)
Here's the test case:
class TestX(TestCase):
def test_write_string_to_channel(self):
test_string = "test_string"
c = Controller()
f = c.mock() # make a mock object
f.write(test_string) # this is the function call we expect
c.replay() # tell the controller that we're going to play back the recorded call
x = X()
x.write_string_to_file(f, test_string) # perform the call
x.verify() # check that writer.write(test_string) was called
class TestX(PyMockTestCase):
def test_write_string_to_channel(self):
test_string = "test_string"
mock_file = self.mock()
mock_file.write(test_string)
self.replay()
x = X()
x.write_string_to_file(f, test_string)
self.verify()
The important thing to notice in the first case is the controller object. It keeps
track of all the mock object states. The PyMockTestCase automatically
constructs a controller for you.