-
Increased speed
— Tests that run quickly are extremely beneficial. E.g. if you have a very resource intensive function, a mock of that function would cut down on unnecessary resource usage during testing, therefore reducing test run time.
-
Avoiding undesired side effects during testing
— If you are testing a function which makes calls to an external API, you may not want to make an actual API call every time you
run your tests
. You’d have to change your code every time that API changes, or there may be some rate limits, but mocking helps you avoid that.
Prerequisites
You will need to have Python 3.3 or higher installed. Get the correct version for your platform
here
. I will be using version 3.6.0 for this tutorial.
Once you have that installed, set up a virtual environment:
class TestCalculator(TestCase):
@patch('main.Calculator.sum', return_value=9)
def test_sum(self, sum):
self.assertEqual(sum(2,3), 9)
We are importing the
patch
decorator from
unittest.mock
. It replaces the actual
sum
function with a mock function that behaves exactly how we want. In this case, our mock function always returns 9. During the lifetime of our test, the
sum
function is replaced with its mock version. Running this test case, we get this output:
While this may seem counter-intuitive at first, remember that mocking allows you to provide a so-called
fake
implementation of the part of your system you are testing. This gives you a lot of flexibility during testing. You’ll see how to provide a custom function to run when your mock is called instead of hard coding a return value in the section titled
Side Effects
.
A More Advanced Example
In this example, we’ll be using the
requests
library to make API calls. You can get it via
pip install
.
response = requests.get("https://jsonplaceholder.typicode.com/posts")
return response.json()
def __repr__(self):
return '<Blog: {}>'.format(self.name)
This code defines a class
Blog
with a
posts
method. Invoking
posts
on the Blog object will trigger an API call to
jsonplaceholder
, a JSON generator API service.
In our test, we want to mock out the unpredictable API call and only test that a Blog object’s posts method returns posts. We will need to
patch
all Blog objects’
posts
methods as follows.
'id': 1,
'title': 'Test Title',
'body': 'Far out in the uncharted backwaters of the unfashionable end of the western spiral arm of the Galaxy\ lies a small unregarded yellow sun.'
response = blog.posts()
self.assertIsNotNone(response)
self.assertIsInstance(response[0], dict)
You can see from the code snippet that the
test_blog_posts
function is decorated with the
@patch
decorator. When a function is decorated using
@patch
, a mock of the class, method or function passed as the target to
@patch
is returned and passed as an argument to the decorated function.
In this case,
@patch
is called with the target
main.Blog
and returns a Mock which is passed to the test function as
MockBlog
. It is important to note that the target passed to
@patch
should be importable in the environment
@patch
is being invoked from. In our case, an import of the form
from main import Blog
should be resolvable without errors.
Also, note that
MockBlog
is a variable name to represent the created mock and can be you can name it however you want.
Calling
blog.posts()
on our mock blog object returns our predefined JSON. Running the tests should pass.
'id': 1,
'title': 'Test Title',
'body': 'Far out in the uncharted backwaters of the unfashionable end of the western spiral arm of the Galaxy\ lies a small unregarded yellow sun.'
response = blog.posts()
self.assertIsNotNone(response)
self.assertIsInstance(response[0], dict)
# Additional assertions
assert MockBlog is main.Blog # The mock is equivalent to the original
assert MockBlog.called # The mock wasP called
blog.posts.assert_called_with() # We called the posts method with no arguments
blog.posts.assert_called_once_with() # We called the posts method once with no arguments
# blog.posts.assert_called_with(1, 2, 3) - This assertion is False and will fail since we called blog.posts with no arguments
blog.reset_mock() # Reset the mock object
blog.posts.assert_not_called() # After resetting, posts has not been called.
As stated earlier, the mock object allows us to test how it was used by checking the way it was called and which arguments were passed, not just the return value.
Mock objects can also be reset to a pristine state i.e. the mock object has not been called yet. This is especially useful when you want to make multiple calls to your mock and want each one to run on a fresh instance of the mock.
Side Effects
These are the things that you want to happen when your mock function is called. Common examples are calling another function or raising exceptions.
Let us revisit our
sum
function. What if, instead of hard coding a return value, we wanted to run a custom
sum
function instead? Our custom function will mock out the undesired long running
time.sleep
call and only remain with the actual summing functionality we want to test. We can simply define a
side_effect
in our test.
class TestCalculator(TestCase):
@patch('main.Calculator.sum', side_effect=mock_sum)
def test_sum(self, sum):
self.assertEqual(sum(2,3), 5)
self.assertEqual(sum(7,3), 10)
Running the tests should pass:
Why in very first example with return value = 9 test is OK? You’re summing 2 and 3, so the test I guess should fail?