Mocking resources in unit tests is just as important and common as writing unit tests. However, a lot of people are not familiar with how to properly mock classes, objects or functions for tests, because the available documentation online is either too short or unnecessarily complicated. One of the main reasons for this confusion — several ways to do the same thing. Every other article out there seems to mock things in a different way. With this series of articles on mocking, I hope to bring some clarity on the topic.
This is a tutorial on Mocking with pytest. I am operating with the assumption that you can write unit tests in Python using
As you are here, reading this article, I will assume that you are familiar with mocking. In case you are not, let us do a quick overview of what it is and why we need it.
Say, you have a service that collects stock market data and gives information about the top gainers in a particular sector. You get the stock market information from a third party API, and process it to give out the results. Now, to test your code, you would not want to hit the API every time, as it will make the tests slower, and also the API provider would charge you for the extra hits. What you want here is a mock! A mock replaces a function with a dummy you can program to do whatever you choose. This is also called ‘Patching’. For the rest of this series, I am going to use ‘mock’ and ‘patch’ interchangeably.
Packages needed for Mocking
Unlike the majority of programming languages, Python comes with a built-in library for unit testing and mocking. They are powerful, self-sufficient and provide the functionality you need. The Pytest-mock plugin we will use, is a convenient wrapper around it which makes it easier to use it in combination with
If you look up articles on mocking, or if you read through the endless questions on Stackoverflow, you will frequently come across the words
patch, etc. I'm going to demystify them here.
In Python, to mock, be it functions, objects or classes, you will mostly use
Mock class comes from the built-in
unittest.mock module. From now on, anytime you come across
Mock, know that it is from the
MagicMock is a subclass of
Mock with some of the magic methods implemented. Magic methods are your usual dunder methods like
For the most part, it does not matter which one you use,
MagicMock. Unless you need magic methods like the above implemented, you can stick to
Mock. Pytest-mock gives you access to both of these classes with an easy to use interface.
patch is another function that comes from the 'unittest' module that helps replace functions with mocks. Pytest mock has a wrapper for this too.
Installing Pytest Mock
Before you get started with using pytest-mock, you have to install it. You can install it with pip as follows:
pip install pytest-mock
This is a pytest plugin. So, it will also install
pytest, if you have not installed it already.
Mocking a simple function
As this is the first article, we will keep it simple. We will start by mocking a simple function.
Say, we have a function
get_operating_system that tells us whether we are using Windows or Linux.
# application.py from time import sleep def is_windows(): # This sleep could be some complex operation instead sleep(5) return True def get_operating_system(): return 'Windows' if is_windows() else 'Linux'
This function uses another function
is_windows to check if the current system is Windows or not. Assume that this
is_windows function is quite complex taking several seconds to run. We can simulate this slow function by making the program sleep for 5 seconds every time it is called.
A pytest for
get_operating_system() would be as follows:
# test_application.py from application import get_operating_system def test_get_operating_system(): assert get_operating_system() == 'Windows'
get_operating_system() calls a slower function
is_windows, the test is going to be slow. This can be seen below in the output of running pytest which took 5.05 seconds.
$ pytest ================ test session starts ======================== Python 3.7.3, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 rootdir: /usr/Personal/Projects/pytest-and-mocking plugins: mock-2.0.0 collected 1 item test_application.py . [100%] ================ 1 passed in 5.05s ==========================
Unit tests should be fast. We should be able to run hundreds of tests in seconds. A single test that takes five seconds slows down the test suite. Enter mocking, to makes our lives easier. If we patch the slow function, we can verify
get_operating_system's behavior without waiting for five seconds.
Let’s mock this function with pytest-mock.
Pytest-mock provides a fixture called
mocker. It provides a nice interface on top of python's built-in mocking constructs. You use
mocker by passing it as an argument to your test function, and calling the mock and patch functions from it.
Say, you want the
is_windows function to return
True without taking those five precious seconds. We can patch it as follows:
You have to refer to
is_windows here as
application.is_windows, given that it is the function in the application module. If we only patch
is_windows, it will try to patch a function called
is_windows in the 'test_application' file, which obviously does not exist. The format is always
<module_name>.<function_name>. Knowing how to mock correctly is important and we will continue working on it in this series.
The updated test function with the patch is as follows:
# 'mocker' fixture provided by pytest-mock def test_get_operating_system(mocker): # Mock the slow function and return True always mocker.patch('application.is_windows', return_value=True) assert get_operating_system() == 'Windows'
Now when you run the test, it will finish much faster.
$ pytest ============ test session starts ================== Python 3.7.3, pytest-5.4.1, py-1.8.1, pluggy-0.13.1 rootdir: /mnt/c/Personal/Projects/pytest-and-mocking plugins: mock-2.0.0 collected 1 item test_application.py . [100%] =========== 1 passed in 0.11s ======================
As you can see, the test only 0.11 seconds. We have successfully patched the slow function and made the test suite faster.
Another advantage of mocking - you can make the mock function return anything. You can even make it raise errors to test how your code behaves in in those scenarios. We will see how all of this works and more, in the future articles.
For now, if you want to test the case where
False, write the following test:
def test_operation_system_is_linux(mocker): mocker.patch('application.is_windows', return_value=False) # set the return value to be False assert get_operating_system() == 'Linux'
Note that all of the mocks & patches set with
mocker are function scoped i.e., they will only be available for that specific function. Therefore, you can patch the same function in multiple tests and they will not conflict with each other.
That is your first introduction to the world of mocking with pytest. We will cover more scenarios in the upcoming articles. Stay tuned, stay safe and stay awesome till then.
List of articles in this series:
Mocking Functions Part I 🢠 Current Article
If you like this article, you can like this article to encourage me to put out the next article soon. If you think someone you know can benefit from this article, do share it with them.
If you want to thank me, you can say hi on twitter @durgaswaroop. And, if you want to support me here’s my paypal link: paypal.me/durgaswaroop
Attribution: Python Logo — https://www.python.org/community/logos/