2025, Sep 21 01:00

Avoid this pytest-mock mistake: set return_value on the mock method, not the call result

Learn why pytest and pytest-mock unit tests fail when you set return_value on dep.do_it() instead of the mock method. See the minimal example and the fix.

When unit-testing a class that delegates work to another class, it’s easy to set up a mock incorrectly and then wonder why the test fails. A common pitfall with pytest and pytest-mock is to set the return value on the result of calling a mock, rather than on the mock method itself. Below is a minimal example of the failure mode and the precise change that makes the test behave as intended.

Example setup

class Dependency:
    def do_it(self):
        return 1

class Service:
    def __init__(self, dep):
        self.dep = dep

    def run(self):
        return self.dep.do_it()


def test_run():
    dep = Dependency()
    svc = Service(dep)
    assert svc.run() == 1


def test_run_with_mock(mocker):
    dep = mocker.Mock()
    svc = Service(dep)
    # incorrect line shown below and fixed later
    # dep.do_it().return_value = 1
    # correct line:
    dep.do_it.return_value = 1
    assert svc.run() == 1
    dep.do_it.assert_called_once_with()

What went wrong

The failing version sets the return value of the object produced by calling the mock, not the return value of the method on the mock itself. Concretely, this line is the culprit:

dep.do_it().return_value = 1

That expression invokes the mock method and then tries to configure the return value of whatever that call produced. As a result, the method that your code under test actually calls remains unconfigured. This is why the mock appears to be “not called” or not behaving as expected. In fact, if you mistakenly configure the return value of the result of the call, an assertion like calling the result again could pass, which underscores how important it is for your test doubles to mirror the real interface they replace.

The fix

Configure the return value on the mocked method itself, not on the result of calling it:

dep.do_it.return_value = 1

After that, the test should pass, and you can also assert that the interaction happened exactly once with no arguments:

dep.do_it.assert_called_once_with()

Why this nuance matters

Mocking is about defining the contract between your subject under test and its collaborators. If you accidentally program the return value of a call result instead of the method, you’re no longer testing the same contract as your production code uses. This subtle mismatch can mask bugs or, worse, give false confidence. It also illustrates why aligning your mock’s surface area with the real dependency is crucial. The same principle holds whether you use pytest-mock’s mocker fixture or instantiate a unittest.mock.Mock() directly; the behavior around return_value is identical.

Takeaways

When stubbing a method’s return value on a mock, set it on the method attribute, not on the result of invoking it. Keep an eye on interaction checks like assert_called_once_with() to verify that your mock is exercised exactly as intended. If the interface of the dependency is very simple, consider passing in a trivial test-specific implementation with the same method signature; this can make the contract explicit and keep tests readable. In any case, ensure the test double you use mirrors the dependency that your production code expects.

The article is based on a question from StackOverflow by user1604008 and an answer by wim.