diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ad89c24..c693be5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,13 @@ +3.2.0 (2020-07-11) +------------------ + +* `AsyncMock `__ is now exposed in ``mocker`` and supports provides assertion introspection similar to ``Mock`` objects. + + Added by `@tirkarthi`_ in `#197`_. + +.. _@tirkarthi: https://github.com/tirkarthi +.. _#197: https://github.com/pytest-dev/pytest-mock/pull/197 + 3.1.1 (2020-05-31) ------------------ diff --git a/src/pytest_mock/plugin.py b/src/pytest_mock/plugin.py index fdf8365..8850231 100644 --- a/src/pytest_mock/plugin.py +++ b/src/pytest_mock/plugin.py @@ -47,6 +47,8 @@ def __init__(self, config): self.MagicMock = mock_module.MagicMock self.NonCallableMock = mock_module.NonCallableMock self.PropertyMock = mock_module.PropertyMock + if hasattr(mock_module, "AsyncMock"): + self.AsyncMock = mock_module.AsyncMock self.call = mock_module.call self.ANY = mock_module.ANY self.DEFAULT = mock_module.DEFAULT @@ -275,6 +277,41 @@ def wrap_assert_called(*args, **kwargs): assert_wrapper(_mock_module_originals["assert_called"], *args, **kwargs) +def wrap_assert_not_awaited(*args, **kwargs): + __tracebackhide__ = True + assert_wrapper(_mock_module_originals["assert_not_awaited"], *args, **kwargs) + + +def wrap_assert_awaited_with(*args, **kwargs): + __tracebackhide__ = True + assert_wrapper(_mock_module_originals["assert_awaited_with"], *args, **kwargs) + + +def wrap_assert_awaited_once(*args, **kwargs): + __tracebackhide__ = True + assert_wrapper(_mock_module_originals["assert_awaited_once"], *args, **kwargs) + + +def wrap_assert_awaited_once_with(*args, **kwargs): + __tracebackhide__ = True + assert_wrapper(_mock_module_originals["assert_awaited_once_with"], *args, **kwargs) + + +def wrap_assert_has_awaits(*args, **kwargs): + __tracebackhide__ = True + assert_wrapper(_mock_module_originals["assert_has_awaits"], *args, **kwargs) + + +def wrap_assert_any_await(*args, **kwargs): + __tracebackhide__ = True + assert_wrapper(_mock_module_originals["assert_any_await"], *args, **kwargs) + + +def wrap_assert_awaited(*args, **kwargs): + __tracebackhide__ = True + assert_wrapper(_mock_module_originals["assert_awaited"], *args, **kwargs) + + def wrap_assert_methods(config): """ Wrap assert methods of mock module so we can hide their traceback and @@ -305,6 +342,26 @@ def wrap_assert_methods(config): patcher.start() _mock_module_patches.append(patcher) + if hasattr(mock_module, "AsyncMock"): + async_wrappers = { + "assert_awaited": wrap_assert_awaited, + "assert_awaited_once": wrap_assert_awaited_once, + "assert_awaited_with": wrap_assert_awaited_with, + "assert_awaited_once_with": wrap_assert_awaited_once_with, + "assert_any_await": wrap_assert_any_await, + "assert_has_awaits": wrap_assert_has_awaits, + "assert_not_awaited": wrap_assert_not_awaited, + } + for method, wrapper in async_wrappers.items(): + try: + original = getattr(mock_module.AsyncMock, method) + except AttributeError: # pragma: no cover + continue + _mock_module_originals[method] = original + patcher = mock_module.patch.object(mock_module.AsyncMock, method, wrapper) + patcher.start() + _mock_module_patches.append(patcher) + if hasattr(config, "add_cleanup"): add_cleanup = config.add_cleanup else: diff --git a/tests/test_pytest_mock.py b/tests/test_pytest_mock.py index 1731583..b89e795 100644 --- a/tests/test_pytest_mock.py +++ b/tests/test_pytest_mock.py @@ -708,6 +708,43 @@ def test(mocker): result.stdout.fnmatch_lines(expected_lines) +@pytest.mark.skipif( + sys.version_info < (3, 8), reason="AsyncMock is present on 3.8 and above" +) +@pytest.mark.usefixtures("needs_assert_rewrite") +def test_detailed_introspection_async(testdir): + """Check that the "mock_use_standalone" is being used. + """ + testdir.makepyfile( + """ + import pytest + + @pytest.mark.asyncio + async def test(mocker): + m = mocker.AsyncMock() + await m('fo') + m.assert_awaited_once_with('', bar=4) + """ + ) + result = testdir.runpytest("-s") + expected_lines = [ + "*AssertionError: expected await not found.", + "*Expected: mock('', bar=4)", + "*Actual: mock('fo')", + "*pytest introspection follows:*", + "*Args:", + "*assert ('fo',) == ('',)", + "*At index 0 diff: 'fo' != ''*", + "*Use -v to get the full diff*", + "*Kwargs:*", + "*assert {} == {'bar': 4}*", + "*Right contains* more item*", + "*{'bar': 4}*", + "*Use -v to get the full diff*", + ] + result.stdout.fnmatch_lines(expected_lines) + + def test_missing_introspection(testdir): testdir.makepyfile( """