Home > Software engineering >  Pytest mocker.patch is returning NonCallableMagicMock
Pytest mocker.patch is returning NonCallableMagicMock

Time:12-17

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)
  • Related