This test is driving me insane and I can't figure it out.
mocker.patch
is returning a MagicMock
(as expected) in my actual tests. However, when it invokes the modules and the class that I wanted to patch is mocked, it is returning a NonCallableMagicMock
, rather than a MagicMock
. So when I do an assert_called_with
, it is failing and throwing an error, because the two are different.
Am I doing the patch incorrectly? I made sure to patch the class that is inside the namespace of the module where it is worked with and not the namespace of the actual module where the class resides. The fact that a NonCallableMagicMock
is returned, leads me to believe that I am patching the correct target.
So if I'm patching correctly, then why am I getting this error? How can I assert that the function is called with a instance of MyQuery
as a param?
I have the following code structure:
.
├── main.py
├── src
│ ├── handler
│ │ └── my_query_handler.py
│ ├── query
│ │ └── my_query.py
│ └── repo
│ └── my_repo.py
└── tests
└── handler
└── test_my_query_handler.py
The code for all the files are as follows:
my_query_handler.py
from src.query.my_query import MyQuery
from src.repo.my_repo import MyRepo
class MyQueryHandler:
def handle(self, repo: MyRepo):
query = MyQuery(value_one="Hello", value_two="World")
result = repo.exec(query=query)
return result
my_query.py
class MyQuery:
_value_one: str
_value_two: str
def __init__(self, value_one: str, value_two: str):
self._value_one = value_one
self._value_two = value_two
def get_query(self) -> str:
return f"{self._value_one} {self._value_two}"
my_repo.py
from src.query.my_query import MyQuery
class MyRepo:
def exec(self, query: MyQuery):
return query.get_query()
test_my_query_handler.py
import pytest
from src.repo.my_repo import MyRepo
from src.handler.my_query_handler import MyQueryHandler
from src.handler.my_query_handler import MyQuery
from unittest.mock import MagicMock
class TestMyQueryHandler:
@pytest.fixture
def mock_query(self, mocker):
namespace = f"{MyQueryHandler.__module__}.{MyQuery.__name__}"
return mocker.patch(namespace, autospec=True)
def test_my_query_handler(self, mock_query):
expected_value = 'Hello World'
mock_repo = MagicMock(spec=MyRepo)
mock_repo.exec.return_value = expected_value
handler = MyQueryHandler()
result = handler.handle(mock_repo)
mock_repo.exec.assert_called_with(query=mock_query)
assert result == expected_value
main.py
from src.handler.my_query_handler import MyQueryHandler
from src.repo.my_repo import MyRepo
handler = MyQueryHandler()
repo = MyRepo()
print(handler.handle(repo))
When I run these tests, the mock that is returned from mock_query
is a MagicMock
:
<MagicMock name='MyQuery' spec='MyQuery' id='4365116176'
However, when I run the tests, when the module is patched, it creates a NonCallableMagicMock
<NonCallableMagicMock name='MyQuery()' spec='MyQuery' id='4365118992'>
And the following error is generated when I do the assert_called_with
_______________________________________________________ TestMyQueryHandler.test_my_query_handler _______________________________________________________
__wrapped_mock_method__ = <function NonCallableMock.assert_called_with at 0x105b407a0>, args = (<MagicMock name='mock.exec' id='4375790672'>,)
kwargs = {'query': <MagicMock name='MyQuery' spec='MyQuery' id='4391261264'>}, __tracebackhide__ = True
msg = "Expected call: exec(query=<MagicMock name='MyQuery' spec='MyQuery' id='4391261264'>)\nActual call: exec(query=<NonCal...)' spec='MyQuery' id='4391264144'>}\n ? ^"
__mock_self = <MagicMock name='mock.exec' id='4375790672'>, actual_args = ()
actual_kwargs = {'query': <NonCallableMagicMock name='MyQuery()' spec='MyQuery' id='4391264144'>}
introspection = "\nKwargs:\nassert {'query': <No...'4391264144'>} == {'query': <Ma...'4391261264'>}\n Differing items:\n {'query': <...)' spec='MyQuery' id='4391264144'>}\n ? ^"
@py_assert2 = None, @py_assert1 = False
def assert_wrapper(
__wrapped_mock_method__: Callable[..., Any], *args: Any, **kwargs: Any
) -> None:
__tracebackhide__ = True
try:
> __wrapped_mock_method__(*args, **kwargs)
env/lib/python3.7/site-packages/pytest_mock/plugin.py:414:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
_mock_self = <MagicMock name='mock.exec' id='4375790672'>, args = (), kwargs = {'query': <MagicMock name='MyQuery' spec='MyQuery' id='4391261264'>}
expected = ((), {'query': <MagicMock name='MyQuery' spec='MyQuery' id='4391261264'>})
_error_message = <function NonCallableMock.assert_called_with.<locals>._error_message at 0x105bb6440>
actual = call(query=<NonCallableMagicMock name='MyQuery()' spec='MyQuery' id='4391264144'>), cause = None
def assert_called_with(_mock_self, *args, **kwargs):
"""assert that the mock was called with the specified arguments.
Raises an AssertionError if the args and keyword args passed in are
different to the last call to the mock."""
self = _mock_self
if self.call_args is None:
expected = self._format_mock_call_signature(args, kwargs)
raise AssertionError('Expected call: %s\nNot called' % (expected,))
def _error_message():
msg = self._format_mock_failure_message(args, kwargs)
return msg
expected = self._call_matcher((args, kwargs))
actual = self._call_matcher(self.call_args)
if expected != actual:
cause = expected if isinstance(expected, Exception) else None
> raise AssertionError(_error_message()) from cause
E AssertionError: Expected call: exec(query=<MagicMock name='MyQuery' spec='MyQuery' id='4391261264'>)
E Actual call: exec(query=<NonCallableMagicMock name='MyQuery()' spec='MyQuery' id='4391264144'>)
../../../.pyenv/versions/3.7.10/lib/python3.7/unittest/mock.py:878: AssertionError
During handling of the above exception, another exception occurred:
self = <test_my_query_handler.TestMyQueryHandler object at 0x105bc9b90>, mock_query = <MagicMock name='MyQuery' spec='MyQuery' id='4391261264'>
def test_my_query_handler(self, mock_query):
expected_value = 'Hello World'
mock_repo = MagicMock(spec=MyRepo)
mock_repo.exec.return_value = expected_value
handler = MyQueryHandler()
result = handler.handle(mock_repo)
> mock_repo.exec.assert_called_with(query=mock_query)
E AssertionError: Expected call: exec(query=<MagicMock name='MyQuery' spec='MyQuery' id='4391261264'>)
E Actual call: exec(query=<NonCallableMagicMock name='MyQuery()' spec='MyQuery' id='4391264144'>)
E
E pytest introspection follows:
E
E Kwargs:
E assert {'query': <No...'4391264144'>} == {'query': <Ma...'4391261264'>}
E Differing items:
E {'query': <NonCallableMagicMock name='MyQuery()' spec='MyQuery' id='4391264144'>} != {'query': <MagicMock name='MyQuery' spec='MyQuery' id='4391261264'>}
E Full diff:
E - {'query': <MagicMock name='MyQuery' spec='MyQuery' id='4391261264'>}
E ? ^^
E {'query': <NonCallableMagicMock name='MyQuery()' spec='MyQuery' id='4391264144'>}
E ? ^
tests/handler/test_my_query_handler.py:34: AssertionError
CodePudding user response:
If you use autospec, and you are mocking a class, the mock behaves like a class. You cannot call instance methods on a class, for that you need an instance, which for a mock you get using return_value
on a class mock.
So to fix your code, you just have to use an instance mock instead of a class mock, either by adapting the fixture:
@pytest.fixture
def mock_query(self, mocker):
namespace = f"{MyQueryHandler.__module__}.{MyQuery.__name__}"
return mocker.patch(namespace, autospec=True).return_value
or by adapting the caller:
result = handler.handle(mock_repo)
mock_repo.exec.assert_called_with(query=mock_query.return_value)