[Python] That's it! A summary of pytest mocks!

Hello!
This is Fukui from the System Development Department!

As the zodiac sign for 2025, the year in which I am writing this blog, is "Snake,"
like to introduce mocks for pytest a Python testing framework

Although the title says "Mock Summary," it is narrowed down to functions and uses that the author has chosen based on his own personal experience and bias, and that he considers to be "essential to know."

So, I hope this will be of some help to those who have not yet used pytest or those who plan to use it in the future

Introduction

[Introduction]

As a premise, this blog will basically describe code using a pytest plugin called
pytest-mock Please note that the notation is different from
unittest.mock in the Python standard library This blog unittest.mock , so if you would like to know more, please refer to the official Python documentation below.
https://docs.python.org/3/library/unittest.mock.html

[For those who have not used pytest yet]

I will also write some sample test code below, but if you have not used pytest before, I would appreciate it if you would read it with the following content in mind

The following is an example of a mock implemented in a test method, but the mocker argument is
a fixture provided by pytest-mock Therefore, if pytest-mock is installed, you can use it without having to import it.

def test_get_name(mocker): mocker.patch.object( (omitted) )

This blog does not explain "What is a fixture?" or "What is mocker?" (about the actual state of mocker), so if you are interested, please refer to the official documentation of pytest and pytest-mock.
https://docs.pytest.org/en/stable/
https://pypi.org/project/pytest-mock/

Mocking methods and properties

Now, let's get to the main part.
First, let's explain mocking methods and properties.

Mocking instance methods

First, let's look at mocking instance methods, which are likely to be used in many cases.
For example, if you have a class called UserInfo that defines a method called get_name() that returns a str type value, the implementation would be as follows:

def test_get_name(mocker): test_name = "Test name" # Mock of get_name() of UserInfo class mocker.patch.object( UserInfo, "get_name", return_value=test_name )

Specify
the target class object and method if you want to specify a return value specify the value of return_value

Mocking a method in a specific module

If you want to mock a method that is not defined in the class, you can also mock it by specifying the module as shown below

def test_get_name(mocker): test_name = "Test name" # Mock get_name() from the app.services.user module mocker.patch( "app.services.user.get_name", return_value=test_name )

You can mock by specifying
target module and the method to be executed within the module as a string When implementing, please be careful not to make any mistakes in specifying the module.

Mocking the same method by specifying different return values ​​when it is executed multiple times

There may be cases where the method being tested executes the same method multiple times.
If it is okay for the return value to be fixed, you can implement the test using the method above, but depending on the process, the processing route may change depending on the return value.

In such cases, side_effect is useful. An example implementation is shown below.

def test_get_name(mocker): test_name_1 = "Ichiro Tanaka" test_name_2 = "Jiro Suzuki" # Mock of get_name() of UserInfo class mocker.patch.object( UserInfo, 'get_name', side_effect=[ test_name_1, # Return value when executed the first time test_name_2, # Return value when executed the second time ] )

side_effect is a convenient property that allows you to customize the behavior of the mock target, and you can specify functions other than the example above, but we will not go into detail here as it would digress from the main topic.
Returning to the main topic, if you want to specify a different value for each execution, you can set it by specifying
the order of the values ​​you want to return in a list, side_effect later, and it is a convenient function that can be used in a variety of other ways.

Mocking Properties

If you want to mock not only methods but also properties, you can use
PropertyMock For example, if the UserInfo class has a property called first_name that stores the name, the implementation would be as follows:

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, the Python standard library, and is used to mock the properties of a specific class, as in this case. (It is a different Mock class from the one used for normal mocks.)
The code itself is not that different from mocking a method, but please note that you need to specify new_callable, as in new_callable=PropertyMock

Execute mock verification

So far we have introduced how to mock methods and properties, but
when actually implementing unit tests, there will be times when you want to verify
whether the mocked process is being called as expected  we will introduce how to " verify the execution of mocked methods "

Verify that it was executed

First, let's look at how to verify only whether it was executed.
If you simply want to verify whether it was called once you can implement it by using the
assert_called_once() An example implementation is shown below .

def test_get_name(mocker): test_name = "Test name" # Mock of get_name() of UserInfo class mock_get_name = mocker.patch.object( UserInfo, "get_name", return_value=test_name ) (※Method execution processing, etc.) # The mock of get_name() was executed once mock_get_name.assert_called_once()

if you want to verify the number of times it is called , assuming it will be executed multiple times ,
you can implement this by using assert and the call_count as shown below

def test_get_name(mocker): test_name = "Test name" # Mock of get_name() of UserInfo class mock_get_name = mocker.patch.object( UserInfo, "get_name", return_value=test_name ) (※Method execution processing, etc.) # The mock of get_name() was executed twice assert mock_get_name.call_count == 2

 

Verify that the execution was performed with the expected arguments

Next we will introduce how to verify
whether the arguments passed to the mocked method are as expected This can be implemented by using a method called
assert_called_with() An example implementation is shown below.

def test_get_name(mocker): test_name = "Test name" # Expected value of argument passed to mocked method expected_user_id = 123 # Mock of get_name() of 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() is the expected value (expected_user_id) mock_get_name.assert_called_with(user_id=expected_user_id)

Verify "whether it was executed" and "arguments" together

So far, we have introduced methods to verify "whether it was executed" and "whether the arguments are as expected" separately, but there is also a way to verify them together.
assert_called_once_with() , you can perform verification without writing separate code.
An example implementation is shown below.

def test_get_name(mocker): test_name = "Test name" # Expected value of argument passed to mocked method expected_user_id = 123 # Mock of get_name() of 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() is the expected value (expected_user_id) and has been executed once mock_get_name.assert_called_once_with(user_id=expected_user_id)

If you assume that the method you are mocking will only be called once, it may be more concise to use () rather than ()

even if that the mocked method will be called multiple times you can use
assert_has_calls() An example implementation is shown below.

def test_get_name(mocker): test_name_1 = "Ichiro Tanaka" test_name_2 = "Jiro Suzuki" # Expected values ​​of arguments passed to the mocked method expected_user_id_tanaka = 1 expected_user_id_suzuki = 2 # Mock of get_name() of UserInfo class mock_get_name = mocker.patch.object( UserInfo, 'get_name', side_effect=[ test_name_1, # Return value when executed the first time test_name_2, # Return value when executed the second time ] ) (※Method execution processing, etc.) # Ensure that get_name() is executed in order with the expected arguments mock_get_name.assert_has_calls([ mocker.call(expected_user_id_tanaka), mocker.call(expected_user_id_suzuki) ])

Get the arguments passed to a mocked method

Finally, although it is not a verification method like the ones above, it is also possible to
obtain the arguments passed to the mocked method These can be obtained from the
call_args.args of the mock object The type of the obtained value is a tuple, and it can be obtained as a positional argument.
An example implementation is shown below.

def test_get_name(mocker): test_name = "Test name" # Mock of get_name() of UserInfo class mock_get_name = mocker.patch.object( UserInfo, "get_name", return_value=test_name ) (※ Method execution processing, etc.) # Get arguments 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 you can get the arguments, you can verify them using assert (although you may not necessarily need to use assert...), and
you can also write other processing using the obtained arguments, so I personally thought it would be good to remember and introduced it.
However, that call_args can only get the "arguments from the last time it was executed," so if you want to get the arguments of a method that is executed multiple times, you should use call_args_list .

Raising an exception using side_effect

Finally, we will show you how to raise an exception using a mock object.
we will use
side_effect , which was mentioned in another topic earlier An example implementation is shown below.

def test_get_name(mocker): test_error_message = "The target user does not exist" # Raise an exception with get_name() of the UserInfo class mock_get_name = mocker.patch.object( UserInfo, "get_name", side_effect=Exception(test_error_message) )

The implementation is also simple; specify the exception class instance you want to raise for side_effect and you're done.
If the method you're testing handles some kind of processing when an exception occurs, you can use this method to pass the processing route when an exception occurs, thereby increasing test coverage.

summary

What did you think?
In this blog, I have introduced methods and implementation methods to use depending on the usage scenario, but I believe that the code itself is simple and easy to implement.
Also, as I wrote at the beginning, this article has been limited to only the things that the author thinks are important to remember, so conversely, there are still many functions and methods that have not been introduced here.
If you are interested, please refer to the official documentation and look for better implementations and useful functions! That's all
for today! See you next time!

If you found this article useful, please click [Like]!
6
Loading...
6 votes, average: 1.00 / 16
2,575
X Facebook Hatena Bookmark pocket

The person who wrote this article

About the author

Hiroto Fukui

He joined Beyond in June 2020 and works in the System Development Department (Yokohama office). He
mainly uses PHP in his work, developing game APIs and web systems, as well as developing private Shopify apps.
He loves music in general, especially Western music, and plays the guitar as a hobby. His favorite TV shows are "Detective! Night Scoop" and "Appear! Ad Street Heaven."