这个测试让我发疯了,我想不通。
mocker.patch
在我的实际测试中返回了一个MagicMock
(正如预期的那样(。然而,当它调用模块并且我想要修补的类被嘲笑时,它返回的是NonCallableMagicMock
,而不是MagicMock
。因此,当我执行assert_called_with
时,它会失败并抛出错误,因为两者不同。
我打错补丁了吗?我确保修补了在使用它的模块的命名空间内的类,而不是该类所在的实际模块的命名空间。返回NonCallableMagicMock
这一事实使我相信我正在修补正确的目标。
那么,如果我打错了,为什么会出现这个错误呢?如何断言函数是以MyQuery
的实例作为参数调用的?
我有以下代码结构:
.
├── main.py
├── src
│ ├── handler
│ │ └── my_query_handler.py
│ ├── query
│ │ └── my_query.py
│ └── repo
│ └── my_repo.py
└── tests
└── handler
└── test_my_query_handler.py
所有文件的代码如下:
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))
当我运行这些测试时,从mock_query
返回的mock是MagicMock
:
<MagicMock name='MyQuery' spec='MyQuery' id='4365116176'
然而,当我运行测试时,当模块被修补时,它会创建一个NonCallableMagicMock
<NonCallableMagicMock name='MyQuery()' spec='MyQuery' id='4365118992'>
当我进行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: %snNot 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
如果您使用autospec,并且您正在模拟一个类,那么mock的行为就像一个类。您不能在类上调用实例方法,因为您需要一个实例,而对于mock,您可以在类mock上使用return_value
来获得该实例。
因此,要修复代码,您只需要使用实例mock而不是类mock,或者通过调整fixture:
@pytest.fixture
def mock_query(self, mocker):
namespace = f"{MyQueryHandler.__module__}.{MyQuery.__name__}"
return mocker.patch(namespace, autospec=True).return_value
或者通过调整呼叫者:
result = handler.handle(mock_repo)
mock_repo.exec.assert_called_with(query=mock_query.return_value)