26

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
Evgenii
  • 36,389
  • 27
  • 134
  • 170

2 Answers2

13

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'
Gang
  • 2,658
  • 3
  • 17
  • 38
  • Thanks for your help, when I run your code `pytest test_hello.py` it gives me the error: "ModuleNotFoundError: No module named 'test_hello'". – Evgenii Apr 13 '18 at 04:14
  • 1
    @Evgenii, you have `test_hello.py` and `test_hello()` function, this could mess up the `import`, and I ran with `python -m pytest test_hello.py` – Gang Apr 13 '18 at 04:24
  • Thanks for your help @Gang, but that's not what I wanted to do. In `test_hellow_differnt_from_module()` you are calling the mocked function `mocked_hello()`. However, I want to call a real function `my_function()` and the verify that it called `hello()`. – Evgenii Apr 15 '18 at 03:43
  • None of these appear in the pytest documentation. How to with pytest ? – Itération 122442 Dec 29 '21 at 13:24
  • why are you introducing side_effect – Vivek Dani Jul 04 '23 at 23:06
7

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.

Joe Mulvey
  • 71
  • 1
  • 2