2023-02-17

unittest patch

Introduction

This article provides a detailed introduction to the use of patch in Python's unittest.

patch

Python's unittest patch() method creates a mock object and swaps references to the specified patch destination object. The main arguments are as follows.

Argument Description
target The object to patch. Specify the module name in string format or specify the object itself.
new The object to replace target with. Optional.
side_effect Function that defines the exception to be raised when the mock is called, the value to be returned, or the callable object. Optional.
return_value Value returned when a mock is called. Optional.
autospec If True, ensures that the new object correctly meets the specifications of the target object.
spec If True, ensures that the new object correctly meets the specifications of the target object.
create A boolean value specifying whether to automatically create attributes for mock calls. Optional, defaults to False.
new_callable If new is not a callable object, specify a callable object to create a callable object.
unsafe If the patching target is a module, a value of False will cause the verification to be performed. Optional; default is True.

new

The new argument to the patch method can be used to completely replace the original object. This argument can be used to create a complete new object as a mock object. The following is an example of replacing a Foo object using the new argument.

python
from unittest.mock import patch

class Foo:
    def bar(self):
        return "original"

with patch('__main__.Foo', new=lambda: "mock"):
    f = Foo()
    assert f.bar() == "mock"

In this example, a call to the bar() method on an instance of the Foo class returns "mock". That is, the original object is replaced by a mock object.

It is also possible to set a dummy value to a global variable.

python
from unittest.mock import patch

def test_my_global_variable():
    with patch('my_module.my_global_variable', new='dummy'):
        assert my_module.my_global_variable == 'dummy'

In this example, my_module.my_global_variable is replaced with the string 'dummy'. The new argument sets the given new value for the global variable.

return_value

The return_value argument can be used to specify the value to be returned when the mock is called. The following example creates a mock that returns the value specified by return_value when my_function is called.

python
from unittest.mock import patch

def my_function():
    return 42

with patch('__main__.my_function', return_value=84):
    result = my_function()
    print(result)  # 84

wraps

The wraps argument allows a mock to mimic the behavior of the object it wraps. That is, the mock will have the attributes and methods of the object it wraps. The following example creates a mock that performs the original my_function when my_function is called.

python
from unittest.mock import patch

def my_function():
    return 42

with patch('__main__.my_function', wraps=my_function):
    result = my_function()
    print(result)  # 42

side_effect

The side_effect argument can be used to specify an exception to be raised when the mock is called. The following example creates a mock that raises a ValueError when my_function is called.

python
from unittest.mock import patch

def my_function():
    return 42

with patch('__main__.my_function', side_effect=ValueError('oops')):
    result = my_function()
    print(result)  # ValueError: oops

patch.object

patch.object() is a method of unittest.mock used to replace the attributes of a given object with a mock object. This method can be used to replace the attributes of any object in the module, such as class instance attributes or global variables. This allows you to configure the test to run in a particular state.

The syntax of patch.object() is as follows.

python
patch.object(target, attribute, new=DEFAULT, **kwargs)
Argument Description
target Specify the object to be patched.
attribute Specify the name of the attribute to be patched.
new Specify a new value for the object. The default value is DEFAULT, which automatically creates a mock object.
**kwargs Used to specify additional patching arguments.

The following is an example of using patch.object().

python
from unittest.mock import patch

class MyClass:
    def __init__(self):
        self.x = 10

def my_function():
    obj = MyClass()
    return obj.x

with patch.object(MyClass, 'x', 20):
    assert my_function() == 20

In this example, the instance attribute x of MyClass is patched and set to 20. Since my_function() creates an instance of MyClass and returns x, the return value of my_function() is 20.

Other examples of patch.object include

  • Replace a method of an object
python
from unittest.mock import patch

class MyClass:
    def my_method(self):
        return "original"

with patch.object(MyClass, "my_method", return_value="mocked"):
    obj = MyClass()
    result = obj.my_method()
    print(result)  # => "mocked"
  • Replace module attribute
python
from unittest.mock import patch

import my_module

with patch.object(my_module, "my_function", return_value="mocked"):
    result = my_module.my_function()
    print(result)  # => "mocked"
  • Raise an Exception
python
from unittest.mock import patch

def my_function():
    raise ValueError("original")

with patch.object(__main__, "my_function", side_effect=RuntimeError("mocked")):
    with pytest.raises(RuntimeError, match="mocked"):
        my_function()

Difference from patch

Both patch and patch.object are methods of unittest.mock to create and configure a mock, but there are differences in usage.

patch takes a string path or object as an argument that specifies the object to be mocked. If the object to be mocked is a class method, it will mock the class name in the string passed as the first argument and replace it with the mock object. Also, patch is intended to be used within a with statement.

On the other hand, patch.object takes an object to be mocked and a string that specifies the attributes of the object as arguments. Even if the object to be mocked is a class method, the object passed as the first argument is itself mocked and used to replace its attributes. patch.object need not be used within a with statement.

Thus, you can use patch.object to mock only certain attributes of an object, but you can use patch to mock the entire object.

For example, suppose you have the following code.

python
class MyClass:
    def my_method(self):
        return 'real'

my_instance = MyClass()

You can mock an entire MyClass using patch as follows

python
from unittest.mock import patch

with patch('my_module.MyClass') as MockClass:
    instance = MockClass.return_value
    instance.my_method.return_value = 'mocked'
    assert my_instance.my_method() == 'mocked'

On the other hand, you can mock only the my_method attribute of MyClass using patch.object as follows.

python
from unittest.mock import patch

with patch.object(my_instance, 'my_method') as mock_method:
    mock_method.return_value = 'mocked'
    assert my_instance.my_method() == 'mocked'

Decorators and with statements

In Python's unittest patch, using decorators and with statements has essentially the same effect, but there are differences in the way they are written.

When using decorators, you can enable mocking for all test methods in a test case. The following is an example of using decorators.

python
import unittest
from unittest.mock import patch

class MyTestCase(unittest.TestCase):
    @patch('mymodule.some_function')
    def test_my_function(self, mock_some_function):
        # test ode with mocked function

If you use the with statement, you can enable mocking only within the with statement. The following is an example of using the with statement.

python
import unittest
from unittest.mock import patch

class MyTestCase(unittest.TestCase):
    def test_my_function(self):
        with patch('mymodule.some_function') as mock_some_function:
            # test code with mocked function

In general, using decorators keeps the code concise. However, if you need to customize the mock for each test method, you can use a with statement. Also, when using the with statement, the mock is automatically cleaned up at the end of the with block, so if something fails during testing, the cleanup will still occur.

Define multiple patches

An example of defining multiple patches is shown below.

For example, suppose you have the following module.

my_module.py
import os

def my_function():
    return os.path.abspath(__file__)

To define multiple patches for this module, do the following.

python
import unittest.mock
import my_module

class TestMyModule(unittest.TestCase):
    def test_my_function(self):
        with unittest.mock.patch('my_module.os'):
            with unittest.mock.patch('my_module.os.path.abspath') as abspath_mock:
                abspath_mock.return_value = 'my/absolute/path'
                result = my_module.my_function()
                self.assertEqual(result, 'my/absolute/path')

In this example, the patch is applied to the os module and its path.abspath function. To apply a patch, use the unittest.mock.patch() method, with the path of the object to which you want to apply the patch as an argument. To apply multiple patches, use nested with statements. Patch objects can also be aliased using the as clause. Here we have an alias of abspath_mock and use the return_value attribute to set the return value of the mock.

To define multiple patch decorators, use the following.

python
from unittest.mock import patch

@patch('module1.function1')
@patch('module2.function2')
def test_my_function(mock_function2, mock_function1):
    # test code
    pass

In this example, two patch decorators are used to patch module1.function1 and module2.function2. In this case, care should be taken to ensure that the order of the test function arguments matches the order of the decorators. That is, mock_function1 is a mock of module1.function1 and mock_function2 is a mock of module2.function2.

References

https://docs.python.org/3/library/unittest.mock.html#the-patchers

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!