Patch#
You can change the behavior of an existing function or method by patching it with unittest.mock.patch
. You just need to specify the target
, which refers to an object in Python. Find out more in the specific documentation page.
import requests
import unittest
from unittest.mock import patch
from sklearn.neighbors import KNeighborsRegressor
Target#
There’s always some mystery associated with specifying the target
for mocking; this section focuses on the details.
Target should be a string in the form ‘package.module.ClassName’. The target is imported and the specified object replaced with the new object, so the target must be importable from the environment you are calling patch()
from. The target is imported when the decorated function is executed, not at decoration time.
Note: All targets in these notebook examples begin with the __main__
section. This allows the patched object to be referenced in the same module as the patching code.
The following example shows really typical case. For testing code that handles API reponse, but it’s typical not ot have access to the API during code development - mocking output of the requesting tool is good option in such case.
def request_user(user_id):
response = requests.get(f"https://im_not_exist/{user_id}")
if response.ok:
return response.text
else:
return "Fail!"
Patching requests.get
to return an object with the attribute ok=True
allows us to ensure that we receive a response in a successful case.
with patch("__main__.requests.get") as mocked_get:
mocked_get.return_value.ok = True
mocked_get.return_value.text = "Success"
print(request_user("User"))
Success
Conversely, simulating an error response case returns the message corresponding to a failure.
with patch("__main__.requests.get") as mocked_get:
mocked_get.return_value.ok = False
mocked_get.return_value.text = "Success"
print(request_user("User"))
Fail!
Class method#
You can modify the behavior of a class method as well, simply by specifying the path to it after the class name.
As an example, consider another common case in my practice. When writing code that needs to handle a machine learning model, you don’t necessarily need to use a specific model. Mocking the model’s results is a good way to verify if everything works correctly. The following cell shows how to define the result of the predict
method of the __main__.KNeighboursRegressor
class.
with patch("__main__.KNeighborsRegressor.predict") as predict:
regressor = KNeighborsRegressor()
predict.return_value = 'predict out'
print(regressor.predict())
predict out
Dict#
With patch.dict
you can redefine old and define new values of the dictionary.
Note: It can also handle dict like objects not only dicts.
Note: By default it leaves all unmentioned values unchanged - with the clear=True
argument you can force python to clear all unmentioned keys.
The following cell creates my_dict
, which we’ll use for experiments, and shows how its content changes under the patch.dict
context manager and outside of it.
my_dict = dict(val=10, val2=3)
with patch.dict("__main__.my_dict", {"hello": 10, "val": 3}):
print(my_dict)
print(my_dict)
{'val': 3, 'val2': 3, 'hello': 10}
{'val': 10, 'val2': 3}
Note: that val2
, which isn’t mentioned when invoking patch.dict
, stores its values in the context manager. The following code does the same, but uses the clear=True
argument, which results in the absence of this field in the result.
with patch.dict("__main__.my_dict", {"hello": 10, "val": 3}, clear=True):
print(my_dict)
{'hello': 10, 'val': 3}
The following code shows a very typical case related to mocking specific values for environment variables - patching os.environ
, which is a dict-like object.
import os
with patch.dict("os.environ", {"MY_VALUE": "13"}):
print(os.environ["MY_VALUE"])
13
Syntax#
There are two general ways to define a patch: through the with
context manager or the @
decorator.
The following example demonstrates patching using a context manager. A function is defined, and we have details about the function call within the context manager block.
def my_function(a, b):
return a + b
with patch("__main__.my_function") as p:
my_function("hello")
print(p.call_args)
call('hello')
Method decoration#
Decoration offers more flexibility in its usage. The function being decorated must include a special argument for the mock object, through which you can control the behavior of the target.
The following example demonstrates a test case where test_method
is decorated so that any call to hello
will be modified within it.
def hello(): return "hello"
class SomeTest(unittest.TestCase):
@patch("__main__.hello", return_value="bye bye")
def test_method(self, mocked_hello):
print(hello())
ans = unittest.main(argv=[''], verbosity=0, exit=False)
del SomeTest
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
bye bye
Class decoration#
Instead of decorating just one method in a test case, you can decorate the entire test case by decorating the whole class.
The following cell shows applying patch
as a decorator on the entire test case, demonstrating that all methods of the class exhibit modified behavior in this case.
def hello(): return "hello"
@patch("__main__.hello", return_value="bye bye")
class SomeTest(unittest.TestCase):
def test_method1(self, mock):
print("method1", hello())
def test_method2(self, mock):
print("method2", hello())
ans = unittest.main(argv=[''], verbosity=0, exit=False)
del SomeTest
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
method1 bye bye
method2 bye bye
Several decorators#
You can patch multiple objects by specifying multiple decorators. For each patch, you must provide a mock argument. The rules for determining which parameter belongs to which patch are as follows:
The parameter corresponding to the method patch comes before the parameter corresponding to the class patch.
It might feel counterintuitive that parameters for decorators specified earlier come later than those for more nested parameters—but this makes sense if you consider how decorators work, as each subsequent decorator wraps the previous one.
The following example demonstrates how this works. There are three decorators: one applied to the entire class and two applied to specific methods. In each method, arguments are called in a way that allows us to identify the type of decoration. The output shows which argument corresponds to each decorator.
import unittest.mock
def fun1(): pass
def fun2(): pass
def fun3(): pass
@patch("__main__.fun1")
class SomeTest(unittest.TestCase):
@patch("__main__.fun3")
@patch("__main__.fun2")
def test_value(
self,
arg1: unittest.mock.MagicMock,
arg2: unittest.mock.MagicMock,
arg3: unittest.mock.MagicMock
):
fun1("Patched by class")
fun2("Inner method patch")
fun3("Outer method patch")
print(arg1.call_args)
print(arg2.call_args)
print(arg3.call_args)
ans = unittest.main(argv=[''], verbosity=0, exit=False)
del SomeTest
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
call('Inner method patch')
call('Outer method patch')
call('Patched by class')
Combining#
The question is: if you combine different options for defining decoration, which modification will take precedence? It appears the priority, in decreasing order, is as follows: context manager, class decorator, method decorator.
The following cell shows a test case decorated to modify the hello
function. However, each method within the class modifies the same function in different ways:
def hello(): return "hello"
@patch("__main__.hello", return_value="class decoration patching")
class SomeTest(unittest.TestCase):
def test_method1(self, mock):
with patch("__main__.hello", return_value="manager patching"):
print("method1", hello())
@patch("__main__.hello", return_value="method patching")
def test_method2(self, mock, mock2):
print("method2", hello())
ans = unittest.main(argv=[''], verbosity=0, exit=False)
del SomeTest
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
method1 manager patching
method2 class decoration patching
As a result, we see messages showing the output from the context manager taking priority over the class decoration messages, and the class decoration messages taking priority over the method decoration messages.
Patch handler#
Mocking with any syntax produces the object that should be the handler for the behavior of the mocked object - this object should be of type MagicMock
.
The following cell implements the function that supposed to be patched, and patches it in two different syntaxes. The code under the patch just prints the type of the object created by the patch
.
def test_func(): pass
# decorator syntax
@patch("__main__.test_func")
def something(ans):
print(type(ans))
something()
# context manager syntax
with patch("__main__.test_func") as ans:
print(type(ans))
<class 'unittest.mock.MagicMock'>
<class 'unittest.mock.MagicMock'>
Patch object#
The patch.object
allows you to specify the targed defined in the current environment as the target for the mock.
The following cell defines the class, that we’ll use as an example.
class MyExample:
def example_method(self):
print("hello")
To mock it’s example_method
with just patch
you have to specify target as a string, in this case it would be __main__.MyExample.example_method
.
The patch.object
allows direct access to the MyExample
. The following cell shows an example of retrieving arguments passed during the call to the mocked method.
with patch.object(target=MyExample, attribute="example_method") as mock:
new = MyExample()
new.example_method(10, 20, 30)
ans = mock.call_args
ans
call(10, 20, 30)
Patch in module#
A really useful practical application is that the module in python is an objece as well, so you can select any attribute of the module and mock it using the patch.object
approach.
The following cell creates the module that we’ll use as an example. It contains mocked_function
, which does nothing and should to be mocked and tested_function
, which returns the output of the tested_function
- so all this will work if mocked_funciton
really replaced mocked_function
.
%%writefile /tmp/module_for_testing.py
def mocked_function(): pass
def tested_function():
return mocked_function()
Overwriting /tmp/module_for_testing.py
The main features of the following cell is how it uses patch.object
: target
takes module attribute
is a name of the function to be patched.
import sys
sys.path.append("/tmp")
import module_for_testing
from module_for_testing import tested_function
with patch.object(
target=module_for_testing,
attribute="mocked_function"
) as mock:
mock.return_value = "I was mocked"
print(tested_function())
I was mocked
Finnaly in the patch
context, tested_function()
returns the value specified in the mock.return_value
.
Create#
In case exact mocked attribute doesn’t exist generally you will get corresponding error. But if you need to test case where attribute must be, you can use create=True
parameter - in such case attribute under consideration will be created as a mock object.
The following cell creates a class that has no attributes in it - we’ll use it for mocking attributes.
class MyExample:
pass
The following cell shows an attempt to mock the hello
attribute of the MyExample
.
try:
with patch.object(target=MyExample, attribute="hello"): pass
except Exception as e:
print(e)
<class '__main__.MyExample'> does not have the attribute 'hello'
Since hello
is not defined in the MyExample
class, it returns an error. In contrast, the following cell shows patch.object
with the parameter create=True
.
with patch.object(target=MyExample, attribute="example_method", create=True): pass
At first it may seem like a useless feature, but the following cell shows case when you may need to use it - suppose we need to test the logic that depends on the presence of the named attribute. The following cell shows a test that triggers a behavior that depends on the presence of the hello
attribute.
class MyExample:
def my_test(self):
if hasattr(self, "hello"):
return 1
return 0
with patch.object(target=MyExample, attribute="hello", create=True) as mock:
print(MyExample().my_test())
1
New#
patch
and patch.object
can set a specific value for the mocked object - using the new
parameter. It’s useful to test cases where some attributes of the mocked object take specific values.
Note The argument passed to the funciton by the decorator and the tartet of the with
statement now will be the value specified in new
, not the mock
object.
The following cell creates a MyExample
class with a value
attribute that is supposed to mock.
class MyExample:
value = 10
The following cell defines the context for the patch.object
that uses new. The context prints the corresponding attribute of the MyExample()
and the target of the context.
with patch.object(
target=MyExample,
attribute="value",
new="hello from mock"
) as value:
print("value:", MyExample().value)
print("handler:", value)
value: hello from mock
handler: hello from mock
Both take the value specified by the new
parameter.