I have a unit test where I want to check if a function was called. How do I do this withpytest
and pytest-mock
libraries?
For example, here is a unit test test_hello.py
. In this test I call the function my_function
and want to verify that it called hello
with a given argument.
def hello(name):
return f'Hello {name}'
def my_function():
hello('Sam')
def test_hello(mocker):
mocker.patch('hello')
my_function()
hello.assert_called_once_with('Sam')
The code above returns the following error:
target = 'hello'
def _get_target(target):
try:
> target, attribute = target.rsplit('.', 1)
E ValueError: not enough values to unpack (expected 2, got 1)
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py:1393: ValueError
During handling of the above exception, another exception occurred:
mocker = <pytest_mock.MockFixture object at 0x109c5e978>
def test_hello(mocker):
> mocker.patch('hello')
test_hello.py:8:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/pytest_mock.py:156: in __call__
return self._start_patch(self.mock_module.patch, *args, **kwargs)
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/pytest_mock.py:134: in _start_patch
p = mock_func(*args, **kwargs)
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py:1544: in patch
getter, attribute = _get_target(target)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
target = 'hello'
def _get_target(target):
try:
target, attribute = target.rsplit('.', 1)
except (TypeError, ValueError):
raise TypeError("Need a valid target to patch. You supplied: %r" %
> (target,))
E TypeError: Need a valid target to patch. You supplied: 'hello'
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/unittest/mock.py:1396: TypeError
Advanced: Mocking in Unit Test In pytest , mocking can replace the return value of a function within a function. This is useful for testing the desired function and replacing the return value of a nested function within that desired function we are testing.
PyTest is a testing framework that allows users to write test codes using Python programming language. It helps you to write simple and scalable test cases for databases, APIs, or UI. PyTest is mainly used for writing tests for APIs. It helps to write tests from simple unit tests to complex functional tests.
The library also provides a function, called patch() , which replaces the real objects in your code with Mock instances. You can use patch() as either a decorator or a context manager, giving you control over the scope in which the object will be mocked.
Pytest has rich inbuilt features which require less piece of code compared to unittest. In the case of unittest, we have to import a module, create a class and then define testing functions inside the class. But in the case of pytest, we have to define the functions and assert the conditions inside them.
Solving the error by
assign a mocked_hello
to mocked.patch
assign a side_effect
to mocked func
def bonjour(name):
return 'bonjour {}'.format(name)
def hello(name):
return 'Hello {}'.format(name)
def my_function():
return hello('Sam')
def test_hellow_differnt_from_module(mocker):
# mocked func with `test_hello.py` as module name
mocked_hello = mocker.patch('test_hello.hello')
# assign side_effect to mocked func
mocked_hello.side_effect = bonjour
# the mocked func return_value changed by side_effect
assert mocked_hello('Sam') == 'bonjour Sam'
# the mocked func called with Sam, but with different return value
mocked_hello.assert_called_with('Sam')
call a real function my_function() and the verify that it called hello
def test_my_function(mocker):
mocker.patch('test_hello.hello', side_effect=bonjour)
mf = my_function()
hello.assert_called_with('Sam')
assert mf == 'bonjour Sam'
I think the minimal change to the OP's code that would make it work would be simply to fully qualify the name of the mocked function. So, for example, if the code is in a file called test.py
:
import pytest
def hello(name):
return f'Hello {name}'
def my_function():
hello('Sam')
def test_hello(mocker):
mocker.patch('test.hello')
my_function()
hello.assert_called_once_with('Sam')
(I'm assuming test.py
is located somewhere in sys.path
.)
However, I think it's better to change the test function to be:
def test_hello(mocker):
mocked_func = mocker.patch('test.hello')
my_function()
mocked_func.assert_called_once_with('Sam')
That form seems clearer to me and is less likely to raise an AttributeError in more complex code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With