Mocking in Python can be initially confusing and the official docs, while informative, don’t make learning it any easier for newcomers. In my case, I came across it when I was still transitioning from Ruby which, I believe, contributed to the confusion. This article takes a gentler approach to learning Python’s mocking library to help you get productive sooner.
Importing Objects: A Review
The unittest.mock library builds on top of how Python implements the import statement so it’s imperative that we have a solid understanding of how it works before we can continue.
Let’s say we have a file named module1.py
with the following code:
1
2
3
4
5
6
# module1.py
class A(object):
def __init__(self):
...
And let’s also say we have a file named module2.py
with the following code:
1
2
3
4
5
6
7
8
# module2.py
from module1 import A
class B(object):
def __init__(self):
...
If you look at module2.py
line 3, what it’s effectively doing is that it’s
creating a new variable named A
that is local to module2
and this variable
points to the actual A
class in memory as defined by module1
.
What this means is that we now have two variables pointing to the same memory
address. These two variables are named module1.A
and module2.A
:
|------------------|
module1.A ---> | |
| <Actual A Class> |
module2.A ---> | |
|------------------|
Now let’s say that, in module2
, we used an unqualified A
as in line 8
of the following:
1
2
3
4
5
6
7
8
# module2.py
from module1 import A
class B(object):
def __init__(self):
self.a = A()
Behind the scenes, Python will try to find an A
variable in the module2
namespace.
After finding the variable, it then uses it to get to the actual A
class in memory.
Effectively, the A()
in line 8 above is shorthand for module2.A()
.
Mocking Objects
Building on top of what we learned in the previous section, let’s say we want
to test our class B
from above. We would then write our test initially as follows:
1
2
3
4
5
6
7
8
# test_module2.py
from module2 import B
class TestB:
def test_initialization(self):
subject = B()
Now let’s say we want to focus our test on the logic of B
and not care about
the internals of A
for now. To do that, we need to mock out the variable
module2.A
since that’s what all unqualified A
’s in module2
will resolve to.
To mock out all unqualified A
’s in module2
, we use the patch decorator as
in line 8 below:
1
2
3
4
5
6
7
8
9
10
# test_module2.py
from mock import patch
from module2 import B
class TestB:
@patch('module2.A')
def test_initialization(self, mock_A):
subject = B()
There’s a lot happening above so let’s break it down:
- Line 3:
from mock import patch
makes the patch decorator available to our tests. - Line 8: Using the patch decorator,
we create an instance of Mock
and make the variable
module2.A
point to this instance.
Reusing our diagram from above, our new reality is as follows:
|------------------|
module1.A ---> | <Actual A Class> |
|------------------|
|------------------|
module2.A ---> | <Mock> |
|------------------|
Our use of the patch
decorator above has another side effect. Namely
that this decorator provides our test method with a reference to the Mock
instance it created. We capture this reference using the mock_A
parameter
in line 9.
So a more complete representation of our new reality is as follows:
|------------------|
module1.A ---> | <Actual A Class> |
|------------------|
|------------------|
module2.A ---> | |
| <Mock> |
mock_A ---> | |
|------------------|
Note that since we are patching
module2.A
via the decorator method, this new reality is effective within the context of thetest_initialization()
method only.
With that, we can now use mock_A
to describe how our mock A
class should
behave:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# test_module2.py
from mock import patch
from module2 import B
class TestB:
@patch('module2.A')
def test_initialization(self, mock_A):
# Mock A's do_something() method
mock_A.do_something.return_value = True
subject = B()
# Check if B called A.do_something() correctly
mock_A.do_something.assert_called_once_with('foo')
And that’s it! You now have the basics of mocking in Python. To dive deeper, visit the official documentation for unittest.mock.
Tips on Mocking Attributes and Methods
Stubbing Attributes
If you want to mock an attribute or property and you don’t care about how many times it was called (usually, you don’t), just stub it like so:
1
mock_A.some_attribute = 'somevalue'
Mocking Instances
When the subject under test (SUT) attempts to instantiate an object from
our Mock A
class above, another Mock object is created and returned. This
new Mock object pretends to be an instance of A
. If you want to customize
how this mock instance of A
behaves, first get a reference to it via
the return_value
attribute:
1
2
3
4
5
6
7
8
9
10
11
# Get a reference to the mock A instance
mock_instance = mock_A.return_value
# Now customize its behavior
mock_instance.say_hello.return_value = "hello!"
# Exercise the SUT
subject = B()
# Make assertions against the mock instance
mock_instance.say_hello.assert_called_once_with('foo')
Returning Different Values
Now what if you want the mocked method to return “hello” on the first call and then “olleh!” in the second call (assuming the SUT calls it twice). You can mock it like so:
1
2
3
4
5
6
7
# Get a reference to the mock A instance
mock_instance = mock_A.return_value
# Now customize its behavior
mock_instance.say_hello.side_effect = ["hello!", "olleh!"]
...
Note how we’re using side_effect
in line 5 instead of return_value
. You can also
assign a function to side_effect
but so far I’ve been able to avoid that
complication by just using a list of return values.
Avoiding Phantom Mocks
One of the gotchas of mocking is that you might end up with behavior that’s
specified in the mock object, but not really implemented in the real object.
The result is that you have code that passes the tests but fails in production.
You can prevent this from happening by setting the autospec
and spec_set
parameters in the patch
decorator as in line 8 below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# test_module2.py
from mock import patch
from module2 import B
class TestB:
@patch('module2.A', autospec=True, spec_set=True)
def test_initialization(self, mock_A):
# Mock A here
subject = B()
# Check calls to A here
By using autospec
above, we are automatically defining the Mock oject with
the same specs as the actual module1.A
class. Likewise, by using spec_set
above, we are “freezing” the specs of the Mock object such that we don’t
accidentally create phantom mock attributes or methods in it.
That’s it for now. If you want to dive deeper into the internals of Python’s mocking library, head on over to the official documentation for unittest.mock.