How to Mock Function in Python
The unittest.mock
or Mock
function is a library for testing in Python that allows you to replace components of your system under test with mock
objects and make assertions about how the parts have been used.
The unittest.mock
gives a core Mock
class, removing the necessity to create a host of stubs all over your test suite.
After executing a process, you can assert which methods or attributes were used and the arguments with which they were called. You can also specify the return values and set the needed attributes in the usual way.
In addition, Mock
provides a patch()
decorator that can handle the patching module and the class level attributes within the test scope, along with a helper sentinel
, for creating unique objects.
Mock
was created for use with unittest
and is based on the action-to-assertion
pattern instead of record-to-replay
, which is used in most mocking frameworks. There is a backport of unittest.mock
for previous versions of Python.
Set Up and Install Pytest Mock in Python
In contrast to unittest
, pytest
is not a built-in Python package and needs installation. This can be installed by entering the below command in the terminal.
pip install pytest
As a note, the best practices for unittest
also apply to pytest
, such that:
- A
tests/
directory is required to contain all unit tests. - File names must always begin with
tests_
. - Function names must always begin with
the test
.
The naming standards must be observed so the checker can find the unit tests performed.
Installing pytest-mock
is required before you can use it. The following is how to install it using pip
:
pip install pytest-mock
This is a plugin for Pytest. Therefore, if you haven’t already done so, it will install Pytest.
Mock a Function in Python
To start, create a simple function get_os()
to tell us whether we are using Windows or Linux as the operating system.
Create a file named app.py
as follows:
from time import sleep
def isWindows():
# This sleep can be some complex operation
sleep(5)
return True
def get_os():
return "Windows is the current OS" if isWindows() else "Linux is the current OS"
This function uses the isWindows
function to determine whether the current operating system is Windows or not. Assuming that this isWindows
function is quite complex and takes several seconds to run, here we can simulate this slow-going function by entering the program to sleep for 5 seconds every time it is called.
The following would be a pytest
for the get_os()
function:
Create a file named test_app.py
to pytest
:
from app import get_os
def test_get_os():
assert get_os() == "Windows is the current OS"
Since get_os()
calls a slower function isWindows
, the test is proceeding to be slow. This can be seen in the below output of executing the pytest
.
It took 5.02 seconds and can vary depending on the current instance.
Unit testing needs to be quick, and we should be able to perform hundreds of tests in a matter of seconds. The test suite is slowed down by a single test that takes 5 seconds, so applying mocking makes our lives easier.
If we patch the slow-going function, we can verify the get_os()
function’s behavior without staying for 5 seconds.
Let us mock this function with pytest-mock
.
Pytest-mock
provides a fixture called a mocker
and a fine interface on top of Python’s integrated mocking constructs. You can employ a mocker by invoking the mock and patch functions from it and sending them as arguments to your test function.
In a case where you want the isWindows
function to return True
without waiting for those precious 5 seconds, we can patch it as follows:
mocker.patch("app.isWindows", return_value=True)
Here, you have to refer to isWindows
as app.isWindows
, given that it is the function in the app
module. If we only patch isWindows
, it will try to patch a function called isWindows
in the test_app
file, which does not exist.
The format is always <module_name>.<function_name>
, and knowing how to mock correctly is essential.
The following describes the modified test function after the patch:
from app import get_os
# using'mocker' fixture provided by pytest-mock
def test_get_os(mocker):
# mocking the slow-going function and returning True always
mocker.patch("app.isWindows", return_value=True)
assert get_os() == "Windows is the current OS"
Now, this test will finish much more quickly when you run it.
As you can see, the test took only 0.02 seconds, so we have successfully patched the slow-going function and accelerated the test suite.
Another advantage of mocking is that you can make the mock function return anything and can even make it raise errors to test how your code processes in those scenarios.
Here, if you want to test the case where isWindows
returns False
, write the following test:
from app import get_os
def test_os_isLinux(mocker):
mocker.patch(
"app.isWindows", return_value=False
) # set the return value to be False
assert get_os() == "Linux is the current OS"
The mocks and patches that come with the mocker are all function-scoped, meaning they can only be used with that particular function. As a result, there won’t be any conflicts between patches for the same function in different tests.
Conclusion
Mocking is the practice of replacing the application component you are testing with a mock, which is a dummy implementation of that component. Through mocking, we can get benefits such as increased speed, where tests that run faster are highly beneficial, avoiding undesired side effects during testing, and so on.
Nimesha is a Full-stack Software Engineer for more than five years, he loves technology, as technology has the power to solve our many problems within just a minute. He have been contributing to various projects over the last 5+ years and working with almost all the so-called 03 tiers(DB, M-Tier, and Client). Recently, he has started working with DevOps technologies such as Azure administration, Kubernetes, Terraform automation, and Bash scripting as well.