[Python] That's all! A summary of pytest mocks!
table of contents [非表示]
Hello!
This is Fukui from the System Development Department!
Now, the zodiac sign for 2025, where I write this blog, is the "snake," so
I would like to introduce you to the mock of pytest , a Python test framework
The title says "Mock Summary," but I focus on the functions and usage that I chose based on my own personal opinion and prejudice based on my own experience and what I would like to keep in mind.
So I hope this will be of some help to anyone who has never tried Pytest yet or is planning to use it in the future.
Introduction
[Introduction]
As a prerequisite, this blog will basically provide code for using a pytest plugin called
pytest-mock Please note that the notation is different from
unittest.mock in the Python standard library This blog will not explain
unittest.mock https://docs.python.org/3/library/unittest.mock.html
[For those who have not touched pytest yet]
I will also include some sample test codes, but if you have not yet tried pytest, we would appreciate your reading with the following in mind.
The following is an example of a mock implemented in a test method, but the mocker in is
the fixture provided by pytest-mock Therefore, if pytest-mock is installed, it can be used without the need for imports or other special needs.
1 | def test_get_name(mocker): mocker.patch. object ( (omitted) ) |
This blog does not explain "What is a fixture?" or "What is mocker? (the reality of mocker)" so if you are interested, please refer to the official pytest and pytest-mock documentation.
https://docs.pytest.org/en/stable/
https://pypi.org/project/pytest-mock/
Mocking method properties
Now, let's get to the main story.
First, we will explain how to mock methods and properties.
Mock instance methods
First, we'll introduce you to mocking instance methods, which are likely to be used in many situations.
For example, if there is a class called UserInfo, and the method that returns a value of type Str, called get_name(), is defined, then an example implementation would be:
1 | def test_get_name(mocker): test_name = "testname" # Mock of get_name() in the UserInfo class mocker.patch.object( UserInfo, "get_name", return_value=test_name ) |
If you specify
class object and method for the first and second arguments, and you want to specify a return value, specify the value of return_value and you're done.
Mocking methods for a specific module
If you want to mock a method that is not defined in a class, you can also specify a module and mock it as follows:
1 | def test_get_name(mocker): test_name = "testname" # Mocking of get_name() in app.services.user module mocker.patch( "app.services.user.get_name", return_value=test_name ) |
Mocking is performed by specifying
the target module and the methods executed within the module as a string When implementing this, be careful not to make any errors in the module specification.
Mock with different return values when the same method is executed multiple times
There may be cases where the method under test executes another same method multiple times.
If the return value is fixed, you can test and implement the test using the above method, but depending on the process, the processing route may change depending on the return value.
A useful part of this situation is side_effect An example implementation is as follows:
1 | def test_get_name(mocker): test_name_1 = "Tanaka Ichiro" test_name_2 = "Suzuki Jiro" # Mock of get_name() in the UserInfo class mocker.patch.object( UserInfo, 'get_name', side_effect=[ test_name_1, # Return value when executed for the first time test_name_2, # Return value when executed for the second time] ) |
side_effect is a convenient property that allows you to customize the behavior of the mock target, and functions can be specified in addition to the examples above, but I will skip this topic as it deviates from the main topic.
Returning to the main topic, if you want to specify a different value for each number of executions, you can set it by specifying it
in the order of the values you want to return Side_effect will be introduced in other ways since then, and it is a convenient feature that can be used in a variety of other ways.
Mock properties
If you want to mock properties, not just methods, you can mock them by using
PropertyMock For example, if the UserInfo class has a property called first_name that stores the name, the implementation example would be as follows:
1 | def test_first_name(mocker): first_name_taro = "taro" # Mock the first_name property of UserInfo mocker.patch.object( UserInfo, "first_name", new_callable=PropertyMock, return_value=first_name_taro ) |
PropertyMock itself is a class provided by unittest.mock, a standard Python library, and is used to mock properties of a particular class, like in this article. (This is a different thing from the Mock class used in normal mocking.)
The code itself is not that different from the method mocking, but please note that new_callable is required, such as new_callable=PropertyMock
Verify mock execution
Up until now, we have introduced how to mock methods and properties, but
when actually implementing unit tests, there are times when you want to verify
whether the mocked process is being called as expected From here, we will introduce you to verifying the execution of mocked methods "
Verify if it was executed
Now, let me show you how to first verify that it was "executed or not."
If you want to simply whether you were called once you can implement it by using the method called
assert_called_once() An example implementation is as follows:
1 | def test_get_name(mocker): test_name = "testname" # Mock of get_name() in the UserInfo class mock_get_name = mocker.patch.object( UserInfo, "get_name", return_value=test_name ) (※Method execution processing, etc.) # Mocking of get_name() has been executed once mock_get_name.assert_called_once() |
if you want to verify the number of calls that are executed multiple times ,
you can implement them by using the properties called assert and call_count as follows
1 | def test_get_name(mocker): test_name = "testname" # Mock of get_name() in the UserInfo class mock_get_name = mocker.patch.object( UserInfo, "get_name", return_value=test_name ) (*Method execution processing, etc.) # Mocking of get_name() has been executed twice assert mock_get_name.call_count == 2 |
Verify whether it was executed with the expected argument
Next, we will introduce how to verify
whether the arguments passed to the mocked method are as expected This can be implemented using the method
assert_called_with() An example implementation is as follows:
1 | def test_get_name(mocker): test_name = "testname" # The expected value of the argument passed to the mocked method expected_user_id = 123 # Mock of get_name() in the UserInfo class mock_get_name = mocker.patch.object( UserInfo, "get_name", return_value=test_name ) (*Method execution processing, etc.) # The argument passed to get_name() must be the expected value (expected_user_id) mock_get_name.assert_called_with(user_id=expected_user_id) |
Together, verify "whether it was executed" and "argument"
Up until now, we have introduced the method of verifying whether it was executed and whether the arguments were as expected separately, but there is also a way to verify this together.
Using the method
assert_called_once_with() An example implementation is as follows:
1 | def test_get_name(mocker): test_name = "test name" # The expected value of the argument passed to the mocked method expected_user_id = 123 # Mock of get_name() in the UserInfo class mock_get_name = mocker.patch.object( UserInfo, "get_name", return_value=test_name ) (*Method execution processing, etc.) # The argument passed to get_name() must be expected (expected_user_id) and executed once mock_get_name.assert_called_once_with(user_id=expected_user_id) |
If you assume that the method to be mocked is only called once, then it would be better to write more simply by using assert_called_once_with() rather than assert_called_with()
even if the mocked method is assumed to be called multiple times you can use
assert_has_calls() An example implementation is as follows:
1 | def test_get_name(mocker): test_name_1 = "Tanaka Ichiro" test_name_2 = "Suzuki Jiro" # The expected value of the arguments passed to the mocked method expected_user_id_tanaka = 1 expected_user_id_suzuki = 2 # Mock of get_name() in the UserInfo class mock_get_name = mocker.patch.object( UserInfo, 'get_name', side_effect=[ test_name_1, # Return value when executed for the first time test_name_2, # Return value when executed for the second time]) (*Method execution processing, etc.) # The arguments expected by get_name() must be executed in order mock_get_name.assert_has_calls([ mocker.call(expected_user_id_tanaka), mocker.call(expected_user_id_suzuki) ]) |
Get the arguments passed to the mocked method
Also, although it is not the above-mentioned validation method, you can also retrieve
the arguments passed to the mocked method It can be obtained from
the mock object's call_args.args The obtained value is of type tuple and can be retrieved as a positional argument.
An example implementation is as follows:
1 | def test_get_name(mocker): test_name = "testname" # Mock of get_name() in the UserInfo class mock_get_name = mocker.patch.object( UserInfo, "get_name", return_value=test_name ) (*Method execution processing, etc.) # Get the argument passed to the mocked method args = mock_get_name.call_args.args # First argument args_1 = args[0] # Second argument args_2 = args[1] |
If the arguments can be obtained, you can use assert to verify them (although there may be no need to use assert on purpose...), and
you can also use the obtained arguments to write other processes, so I personally wanted to remember this and introduced them.
However, please note that call_args can only retrieve "the arguments from the last execution" , so if you want to retrieve arguments for methods that are executed multiple times, use call_args_list
Use side_effect to raise exceptions
Finally, we'll show you how to use mock objects to raise exceptions.
we will use
side_effect , which came up in another topic earlier An example implementation is as follows:
1 | def test_get_name(mocker): test_error_message = "The target user does not exist" # raises exception with get_name() of UserInfo class mock_get_name = mocker.patch.object( UserInfo, "get_name", side_effect=Exception(test_error_message) ) |
The implementation itself is also simple, and you can complete the process by specifying
the exception class instance you want to raise for side_effect If the method under test is doing something when an exception occurs, you can use this method to increase the coverage of the test by passing the processing route when an exception occurs.
summary
What did you think?
In this blog, we have introduced the methods and implementation methods used depending on the situation we use, but I think the code itself was simple and easy to implement.
Also, as I wrote at the beginning, I'm only focusing on the content I want to remember, so to put it in the opposite direction, there are still many functions and methods that I haven't introduced here.
If you're interested, please refer to the official documentation to find out better implementations and useful features!
That's all for this time! See you next time!