Chase Seibert
Director of Engineering at Dropbox
- San Francisco, CA
- GitHub
- Goodreads
- Letterboxd
- Mastodon
- Stack Overflow
Python Mock Cookbook
The pythonmock library is one of the awesome things about working in Python. No matter what code you’re unit testing, it’s possible to mock out various pieces with very little test code. That being said, it’s sometimes difficult to figure out the exact syntax for your situation. I attribute this to the nature of how you apply the mocks. Sometimes it feel like you’re shooting in the dark.
Theofficial documentation is comprehensive, but I find it somewhat hard to locate what you’re looking for. I recommend theirexamples doc.
This post is a write-up of my own personal usage.
Big Upfront Caveat
The biggest mistake people make is mocking something out in the wrong place.You always need to mock the thing where it’s imported TO, not where it’s imported FROM. Translation: if you’re importingfrom foo import bar
into a packagebat.baz
, you need to mock it as@mock.patch('bat.baz.bar')
. This can be confusing if you think you should be mocking it where it’s defined, not where it’s used.
Setup
For all these sections, assume we’re in a package calledmyapp
. The code you’re testing is in a module atmyapp.app
and the definition of the objects that you’re mocking is imported there frommyapp.lib
.
Want to see the full code? I have an repository on git with these examples calledpython-mocking.
Constants
The easiest things to mock out are constants.
@mock.patch('myapp.app.MAX_ITEMS',7)deftest_constant(self):...
Functions
For functions, you will commonly need to specify a return value, check if they were called, and with what values.
@mock.patch('myapp.app.get_first_name')deftest_function(self,mock_get_first_name):mock_get_first_name.return_value='Bat'...mock_get_first_name.assert_called()mock_get_first_name.assert_called_once_with('baz')
Methods
Mocking a method on a class is just like mocking a function, you just reference it through the class name.
@mock.patch('myapp.app.Car.get_make')deftest_method(self,mock_get_make):mock_get_make.return_value='Ford'...mock_get_make.assert_called()
Properties
These are just special methods on a class with the@property
decorator. Now we’re starting to get tricky.
@mock.patch('myapp.app.Car.wheels',new_callable=mock.PropertyMock)deftest_property(self,mock_wheels):mock_wheels.return_value=2...
Entire classes
What if you want to swap out an entire class implementation? No problem! The key is that thereturn_value
should be a new instance of the class.
@mock.patch('myapp.app.Car')deftest_class(self,mock_car):classNewCar(object):defget_make(self):return'Audi'@propertydefwheels(self):return6mock_car.return_value=NewCar()...
Class Methods
What about a@classmethod
on a class? It’s the same as a method.
@mock.patch('myapp.app.Car.for_make')deftest_classmethod(self,mock_for_make):new_car=Car()new_car.make='Chevy'mock_for_make.return_value=new_car...
Static Methods
Static methods are the same as class methods.
@mock.patch('myapp.app.Car.roll_call')deftest_classmethod(self,mock_get_roll_call):mock_get_roll_call.return_value=[Car('Ford'),]...
Decorators & Context Managers
Decorators are a tough one. They are defined at import time, and are thus diffucult to re-define as a mock. Your best bet is to create a function for the body of the decorator, and mock that.
Context managers are more do-able, but tricky.
@mock.patch('myapp.app.open_car')deftest_context_manager(self,mock_open_car):defenter_car(car):passmock_open_car.return_value.__enter__=enter_car...
Bonus - Mocking All Tests in a Suite
Say you have a certain mock that you want to apply to all tests in a TestCase class. You have two options. You can apply the patch in thesetUp
and un-apply the patch intearDown
, or you can over-riderun
.
defrun(self,result=None):withmock.patch('myapp.app.foo')asfoo:self.foo=foosuper(MyTestCase,self).run(result)
Alternatively, you can mock out something insetUp
:
defsetUp(self):patcher=mock.patch('myapp.app.foo')self.mock_foo=patcher.start()self.addCleanup(patcher.stop)super(NWApiTestCase,self).setUp()