Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ignore chained calls in assert_has_calls with pytest mocker

When using mocker from pytest-mock, how can I use has_calls to check the calls to a specific method without checking all the calls made to the return value of that method?

For example:

import subprocess
from unittest import mock

def test_foo(mocker):
    m = mocker.patch('test_main.subprocess')
    r1 = subprocess.run(['foo'])
    r1.check_returncode()
    r2 = subprocess.run(['bar'])
    r2.check_returncode()

    m.run.assert_has_calls([
        mock.call(['foo']),
        mock.call(['bar']),
    ])

Fails with:

E       AssertionError: Calls not found.
E       Expected: [call(['foo']), call(['bar'])]
E       Actual: [call(['foo']),
E        call().check_returncode(),
E        call(['bar']),
E        call().check_returncode()]

Why are calls to the return value also included? I just want to check that I had a call to subprocess.run(['foo']) followed by a call to subprocess.run(['bar']). I don't care about calls made to anything it returns, but I do care about the order of calls to run itself.

like image 856
rcorre Avatar asked Sep 07 '25 13:09

rcorre


1 Answers

Add any_order=True to your assert_has_calls call, like this:

m.run.assert_has_calls([
    mock.call(['foo']),
    mock.call(['bar']),
], any_order=True)

The reason is, it looks like assert_has_calls uses the mock_calls attribute on a mock which based on the documentation (https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.mock_calls):

records all calls to the mock object, its methods, magic methods and return value mocks.

With assert_has_calls though, it's not an exact matching problem it was just the ordering that was failing.

EDIT: Since order matters, use call_args_list. Like this:

def test_foo(mocker):
    m = mocker.patch('subprocess_test.subprocess')
    r1 = subprocess.run(['foo'])
    r1.check_returncode()
    r2 = subprocess.run(['bar'])
    r2.check_returncode()

    assert [mock.call(['foo']), mock.call(['bar'])] == m.run.call_args_list
like image 199
wholevinski Avatar answered Sep 09 '25 09:09

wholevinski