diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee43d32..ba271d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -19,9 +19,9 @@ repos: language: python additional_dependencies: [pygments, restructuredtext_lint] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.780 # NOTE: keep this in sync with tox.ini + rev: v0.782 # NOTE: keep this in sync with tox.ini hooks: - id: mypy - files: ^src + files: ^(src|tests) args: [] additional_dependencies: [pytest>=6] diff --git a/setup.cfg b/setup.cfg index fc0ed5f..2b6bae7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,13 @@ [mypy] +disallow_any_generics = True +disallow_incomplete_defs = True +disallow_subclassing_any = True +no_implicit_optional = True pretty = True +show_error_codes = True +strict_equality = True +warn_redundant_casts = True +warn_return_any = True +warn_unreachable = True +warn_unused_configs = True +warn_unused_ignores = True diff --git a/setup.py b/setup.py index 0ad6975..8504f2d 100644 --- a/setup.py +++ b/setup.py @@ -8,8 +8,9 @@ packages=find_packages(where="src"), package_dir={"": "src"}, platforms="any", + package_data={"pytest_mock": ["py.typed"],}, python_requires=">=3.5", - install_requires=["pytest>=2.7"], + install_requires=["pytest>=5.0"], use_scm_version={"write_to": "src/pytest_mock/_version.py"}, setup_requires=["setuptools_scm"], url="https://github.com/pytest-dev/pytest-mock/", diff --git a/src/pytest_mock/__init__.py b/src/pytest_mock/__init__.py index 3e2f241..06cde66 100644 --- a/src/pytest_mock/__init__.py +++ b/src/pytest_mock/__init__.py @@ -1,2 +1,12 @@ from pytest_mock.plugin import * -from pytest_mock.plugin import _get_mock_module + +__all__ = [ + "MockerFixture", + "pytest_addoption", + "pytest_configure", + "session_mocker", + "package_mocker", + "module_mocker", + "class_mocker", + "mocker", +] diff --git a/src/pytest_mock/plugin.py b/src/pytest_mock/plugin.py index 5340218..3ebaefb 100644 --- a/src/pytest_mock/plugin.py +++ b/src/pytest_mock/plugin.py @@ -1,6 +1,16 @@ +import builtins +import unittest.mock +from typing import cast, Generator, Mapping, Iterable, Tuple from typing import Any +from typing import Callable from typing import Dict +from typing import List +from typing import Optional +from typing import Union + + +from unittest import mock import asyncio import functools import inspect @@ -30,15 +40,15 @@ def _get_mock_module(config): return _get_mock_module._module -class MockFixture: +class MockerFixture: """ Fixture that provides the same interface to functions in the mock module, ensuring that they are uninstalled at the end of each test. """ - def __init__(self, config): - self._patches = [] # list of mock._patch objects - self._mocks = [] # list of MagicMock objects + def __init__(self, config: Any) -> None: + self._patches = [] # type: List[Any] + self._mocks = [] # type: List[Any] self.mock_module = mock_module = _get_mock_module(config) self.patch = self._Patcher(self._patches, self._mocks, mock_module) # aliases for convenience @@ -55,14 +65,12 @@ def __init__(self, config): self.sentinel = mock_module.sentinel self.mock_open = mock_module.mock_open - def resetall(self): - """ - Call reset_mock() on all patchers started by this fixture. - """ + def resetall(self) -> None: + """Call reset_mock() on all patchers started by this fixture.""" for m in self._mocks: m.reset_mock() - def stopall(self): + def stopall(self) -> None: """ Stop all patchers started by this fixture. Can be safely called multiple times. @@ -72,9 +80,9 @@ def stopall(self): self._patches[:] = [] self._mocks[:] = [] - def spy(self, obj, name): + def spy(self, obj: object, name: str) -> unittest.mock.MagicMock: """ - Creates a spy of method. It will run method normally, but it is now + Create a spy of method. It will run method normally, but it is now possible to use `mock` call features with it, like call count. :param object obj: An object. @@ -91,7 +99,7 @@ def spy(self, obj, name): # Bypass class descriptor: # http://stackoverflow.com/questions/14187973/python3-check-if-method-is-static try: - value = obj.__getattribute__(obj, name) + value = obj.__getattribute__(obj, name) # type:ignore except AttributeError: pass else: @@ -132,16 +140,19 @@ async def async_wrapper(*args, **kwargs): spy_obj.spy_exception = None return spy_obj - def stub(self, name=None): + def stub(self, name: Optional[str] = None) -> unittest.mock.MagicMock: """ - Creates a stub method. It accepts any arguments. Ideal to register to + Create a stub method. It accepts any arguments. Ideal to register to callbacks in tests. :param name: the constructed stub's name as used in repr :rtype: mock.MagicMock :return: Stub object. """ - return self.mock_module.MagicMock(spec=lambda *args, **kwargs: None, name=name) + return cast( + unittest.mock.MagicMock, + self.mock_module.MagicMock(spec=lambda *args, **kwargs: None, name=name), + ) class _Patcher: """ @@ -149,18 +160,22 @@ class _Patcher: etc. We need this indirection to keep the same API of the mock package. """ + DEFAULT = object() + def __init__(self, patches, mocks, mock_module): self._patches = patches self._mocks = mocks self.mock_module = mock_module - def _start_patch(self, mock_func, *args, **kwargs): + def _start_patch( + self, mock_func: Any, *args: Any, **kwargs: Any + ) -> unittest.mock.MagicMock: """Patches something by calling the given function from the mock module, registering the patch to stop it later and returns the mock object resulting from the mock call. """ p = mock_func(*args, **kwargs) - mocked = p.start() + mocked = p.start() # type: unittest.mock.MagicMock self._patches.append(p) if hasattr(mocked, "reset_mock"): self._mocks.append(mocked) @@ -173,29 +188,105 @@ def _start_patch(self, mock_func, *args, **kwargs): ) return mocked - def object(self, *args, **kwargs): + def object( + self, + target: object, + attribute: str, + new: object = DEFAULT, + spec: Optional[object] = None, + create: bool = False, + spec_set: Optional[object] = None, + autospec: Optional[object] = None, + new_callable: object = None, + **kwargs: Any + ) -> unittest.mock.MagicMock: """API to mock.patch.object""" - return self._start_patch(self.mock_module.patch.object, *args, **kwargs) - - def multiple(self, *args, **kwargs): + if new is self.DEFAULT: + new = self.mock_module.DEFAULT + return self._start_patch( + self.mock_module.patch.object, + target, + attribute, + new=new, + spec=spec, + create=create, + spec_set=spec_set, + autospec=autospec, + new_callable=new_callable, + **kwargs + ) + + def multiple( + self, + target: builtins.object, + spec: Optional[builtins.object] = None, + create: bool = False, + spec_set: Optional[builtins.object] = None, + autospec: Optional[builtins.object] = None, + new_callable: Optional[builtins.object] = None, + **kwargs: Any + ) -> Dict[str, unittest.mock.MagicMock]: """API to mock.patch.multiple""" - return self._start_patch(self.mock_module.patch.multiple, *args, **kwargs) - - def dict(self, *args, **kwargs): + return self._start_patch( + self.mock_module.patch.multiple, + target, + spec=spec, + create=create, + spec_set=spec_set, + autospec=autospec, + new_callable=new_callable, + **kwargs + ) + + def dict( + self, + in_dict: Mapping[Any, Any], + values: Union[Mapping[Any, Any], Iterable[Tuple[Any, Any]]] = (), + clear: bool = False, + **kwargs: Any + ) -> Any: """API to mock.patch.dict""" - return self._start_patch(self.mock_module.patch.dict, *args, **kwargs) - - def __call__(self, *args, **kwargs): + return self._start_patch( + self.mock_module.patch.dict, + in_dict, + values=values, + clear=clear, + **kwargs + ) + + def __call__( + self, + target: str, + new: builtins.object = DEFAULT, + spec: Optional[builtins.object] = None, + create: bool = False, + spec_set: Optional[builtins.object] = None, + autospec: Optional[builtins.object] = None, + new_callable: Optional[builtins.object] = None, + **kwargs: Any + ) -> unittest.mock.MagicMock: """API to mock.patch""" - return self._start_patch(self.mock_module.patch, *args, **kwargs) - - -def _mocker(pytestconfig): + if new is self.DEFAULT: + new = self.mock_module.DEFAULT + return self._start_patch( + self.mock_module.patch, + target, + new=new, + spec=spec, + create=create, + spec_set=spec_set, + autospec=autospec, + new_callable=new_callable, + **kwargs + ) + + +def _mocker(pytestconfig: Any) -> Generator[MockerFixture, None, None]: """ - return an object that has the same interface to the `mock` module, but + Return an object that has the same interface to the `mock` module, but takes care of automatically undoing all patches after each test method. """ - result = MockFixture(pytestconfig) + result = MockerFixture(pytestconfig) yield result result.stopall() @@ -207,11 +298,13 @@ def _mocker(pytestconfig): session_mocker = pytest.yield_fixture(scope="session")(_mocker) -_mock_module_patches = [] +_mock_module_patches = [] # type: List[Any] _mock_module_originals = {} # type: Dict[str, Any] -def assert_wrapper(__wrapped_mock_method__, *args, **kwargs): +def assert_wrapper( + __wrapped_mock_method__: Callable[..., Any], *args: Any, **kwargs: Any +) -> None: __tracebackhide__ = True try: __wrapped_mock_method__(*args, **kwargs) @@ -236,81 +329,81 @@ def assert_wrapper(__wrapped_mock_method__, *args, **kwargs): if introspection: msg += "\n\npytest introspection follows:\n" + introspection e = AssertionError(msg) - e._mock_introspection_applied = True + e._mock_introspection_applied = True # type:ignore[attr-defined] raise e -def wrap_assert_not_called(*args, **kwargs): +def wrap_assert_not_called(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_not_called"], *args, **kwargs) -def wrap_assert_called_with(*args, **kwargs): +def wrap_assert_called_with(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_called_with"], *args, **kwargs) -def wrap_assert_called_once(*args, **kwargs): +def wrap_assert_called_once(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_called_once"], *args, **kwargs) -def wrap_assert_called_once_with(*args, **kwargs): +def wrap_assert_called_once_with(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_called_once_with"], *args, **kwargs) -def wrap_assert_has_calls(*args, **kwargs): +def wrap_assert_has_calls(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_has_calls"], *args, **kwargs) -def wrap_assert_any_call(*args, **kwargs): +def wrap_assert_any_call(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_any_call"], *args, **kwargs) -def wrap_assert_called(*args, **kwargs): +def wrap_assert_called(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_called"], *args, **kwargs) -def wrap_assert_not_awaited(*args, **kwargs): +def wrap_assert_not_awaited(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_not_awaited"], *args, **kwargs) -def wrap_assert_awaited_with(*args, **kwargs): +def wrap_assert_awaited_with(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_awaited_with"], *args, **kwargs) -def wrap_assert_awaited_once(*args, **kwargs): +def wrap_assert_awaited_once(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_awaited_once"], *args, **kwargs) -def wrap_assert_awaited_once_with(*args, **kwargs): +def wrap_assert_awaited_once_with(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_awaited_once_with"], *args, **kwargs) -def wrap_assert_has_awaits(*args, **kwargs): +def wrap_assert_has_awaits(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_has_awaits"], *args, **kwargs) -def wrap_assert_any_await(*args, **kwargs): +def wrap_assert_any_await(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_any_await"], *args, **kwargs) -def wrap_assert_awaited(*args, **kwargs): +def wrap_assert_awaited(*args: Any, **kwargs: Any) -> None: __tracebackhide__ = True assert_wrapper(_mock_module_originals["assert_awaited"], *args, **kwargs) -def wrap_assert_methods(config): +def wrap_assert_methods(config: Any) -> None: """ Wrap assert methods of mock module so we can hide their traceback and add introspection information to specified argument asserts. @@ -360,15 +453,10 @@ def wrap_assert_methods(config): patcher.start() _mock_module_patches.append(patcher) - if hasattr(config, "add_cleanup"): - add_cleanup = config.add_cleanup - else: - # pytest 2.7 compatibility - add_cleanup = config._cleanup.append - add_cleanup(unwrap_assert_methods) + config.add_cleanup(unwrap_assert_methods) -def unwrap_assert_methods(): +def unwrap_assert_methods() -> None: for patcher in _mock_module_patches: try: patcher.stop() @@ -385,7 +473,7 @@ def unwrap_assert_methods(): _mock_module_originals.clear() -def pytest_addoption(parser): +def pytest_addoption(parser: Any) -> None: parser.addini( "mock_traceback_monkeypatch", "Monkeypatch the mock library to improve reporting of the " @@ -400,16 +488,17 @@ def pytest_addoption(parser): ) -def parse_ini_boolean(value): - if value in (True, False): +def parse_ini_boolean(value: Union[bool, str]) -> bool: + if isinstance(value, bool): return value - try: - return {"true": True, "false": False}[value.lower()] - except KeyError: - raise ValueError("unknown string for bool: %r" % value) + if value.lower() == "true": + return True + if value.lower() == "false": + return False + raise ValueError("unknown string for bool: %r" % value) -def pytest_configure(config): +def pytest_configure(config: Any) -> None: tb = config.getoption("--tb", default="auto") if ( parse_ini_boolean(config.getini("mock_traceback_monkeypatch")) diff --git a/src/pytest_mock/py.typed b/src/pytest_mock/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_pytest_mock.py b/tests/test_pytest_mock.py index b89e795..129bc88 100644 --- a/tests/test_pytest_mock.py +++ b/tests/test_pytest_mock.py @@ -2,9 +2,11 @@ import platform import sys from contextlib import contextmanager +from typing import Callable, Any, Tuple, Generator +from unittest.mock import MagicMock -import py.code import pytest +from pytest_mock import MockerFixture pytest_plugins = "pytester" @@ -48,7 +50,9 @@ def ls(cls, path): @pytest.fixture -def check_unix_fs_mocked(tmpdir, mocker): +def check_unix_fs_mocked( + tmpdir: Any, mocker: MockerFixture +) -> Callable[[Any, Any], None]: """ performs a standard test in a UnixFS, assuming that both `os.remove` and `os.listdir` have been mocked previously. @@ -78,15 +82,15 @@ def check(mocked_rm, mocked_ls): return check -def mock_using_patch_object(mocker): +def mock_using_patch_object(mocker: MockerFixture) -> Tuple[MagicMock, MagicMock]: return mocker.patch.object(os, "remove"), mocker.patch.object(os, "listdir") -def mock_using_patch(mocker): +def mock_using_patch(mocker: MockerFixture) -> Tuple[MagicMock, MagicMock]: return mocker.patch("os.remove"), mocker.patch("os.listdir") -def mock_using_patch_multiple(mocker): +def mock_using_patch_multiple(mocker: MockerFixture) -> Tuple[MagicMock, MagicMock]: r = mocker.patch.multiple("os", remove=mocker.DEFAULT, listdir=mocker.DEFAULT) return r["remove"], r["listdir"] @@ -94,7 +98,11 @@ def mock_using_patch_multiple(mocker): @pytest.mark.parametrize( "mock_fs", [mock_using_patch_object, mock_using_patch, mock_using_patch_multiple] ) -def test_mock_patches(mock_fs, mocker, check_unix_fs_mocked): +def test_mock_patches( + mock_fs: Any, + mocker: MockerFixture, + check_unix_fs_mocked: Callable[[Any, Any], None], +) -> None: """ Installs mocks into `os` functions and performs a standard testing of mock functionality. We parametrize different mock methods to ensure @@ -108,7 +116,7 @@ def test_mock_patches(mock_fs, mocker, check_unix_fs_mocked): mocker.stopall() -def test_mock_patch_dict(mocker): +def test_mock_patch_dict(mocker: MockerFixture) -> None: """ Testing :param mock: @@ -120,7 +128,7 @@ def test_mock_patch_dict(mocker): assert x == {"original": 1} -def test_mock_patch_dict_resetall(mocker): +def test_mock_patch_dict_resetall(mocker: MockerFixture) -> None: """ We can call resetall after patching a dict. :param mock: @@ -146,16 +154,16 @@ def test_mock_patch_dict_resetall(mocker): "sentinel", ], ) -def test_mocker_aliases(name, pytestconfig): - from pytest_mock import _get_mock_module, MockFixture +def test_mocker_aliases(name: str, pytestconfig: Any) -> None: + from pytest_mock.plugin import _get_mock_module mock_module = _get_mock_module(pytestconfig) - mocker = MockFixture(pytestconfig) + mocker = MockerFixture(pytestconfig) assert getattr(mocker, name) is getattr(mock_module, name) -def test_mocker_resetall(mocker): +def test_mocker_resetall(mocker: MockerFixture) -> None: listdir = mocker.patch("os.listdir") open = mocker.patch("os.open") @@ -171,21 +179,21 @@ def test_mocker_resetall(mocker): class TestMockerStub: - def test_call(self, mocker): + def test_call(self, mocker: MockerFixture) -> None: stub = mocker.stub() stub("foo", "bar") stub.assert_called_once_with("foo", "bar") - def test_repr_with_no_name(self, mocker): + def test_repr_with_no_name(self, mocker: MockerFixture) -> None: stub = mocker.stub() assert "name" not in repr(stub) - def test_repr_with_name(self, mocker): + def test_repr_with_name(self, mocker: MockerFixture) -> None: test_name = "funny walk" stub = mocker.stub(name=test_name) assert "name={!r}".format(test_name) in repr(stub) - def __test_failure_message(self, mocker, **kwargs): + def __test_failure_message(self, mocker: MockerFixture, **kwargs: Any) -> None: expected_name = kwargs.get("name") or "mock" if NEW_FORMATTING: msg = "expected call not found.\nExpected: {0}()\nActual: not called." @@ -197,15 +205,15 @@ def __test_failure_message(self, mocker, **kwargs): stub.assert_called_with() assert str(exc_info.value) == expected_message - def test_failure_message_with_no_name(self, mocker): + def test_failure_message_with_no_name(self, mocker: MagicMock) -> None: self.__test_failure_message(mocker) @pytest.mark.parametrize("name", (None, "", "f", "The Castle of aaarrrrggh")) - def test_failure_message_with_name(self, mocker, name): + def test_failure_message_with_name(self, mocker: MagicMock, name: str) -> None: self.__test_failure_message(mocker, name=name) -def test_instance_method_spy(mocker): +def test_instance_method_spy(mocker: MockerFixture) -> None: class Foo: def bar(self, arg): return arg * 2 @@ -215,13 +223,13 @@ def bar(self, arg): spy = mocker.spy(foo, "bar") assert foo.bar(arg=10) == 20 assert other.bar(arg=10) == 20 - foo.bar.assert_called_once_with(arg=10) - assert foo.bar.spy_return == 20 + foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined] + assert foo.bar.spy_return == 20 # type:ignore[attr-defined] spy.assert_called_once_with(arg=10) assert spy.spy_return == 20 -def test_instance_method_spy_exception(mocker): +def test_instance_method_spy_exception(mocker: MockerFixture) -> None: class Foo: def bar(self, arg): raise Exception("Error with {}".format(arg)) @@ -235,11 +243,11 @@ def bar(self, arg): foo.bar(arg=v) expected_calls.append(mocker.call(arg=v)) - assert foo.bar.call_args_list == expected_calls + assert foo.bar.call_args_list == expected_calls # type:ignore[attr-defined] assert str(spy.spy_exception) == "Error with {}".format(v) -def test_spy_reset(mocker): +def test_spy_reset(mocker: MockerFixture) -> None: class Foo(object): def bar(self, x): if x == 0: @@ -265,7 +273,7 @@ def bar(self, x): @skip_pypy -def test_instance_method_by_class_spy(mocker): +def test_instance_method_by_class_spy(mocker: MockerFixture) -> None: class Foo: def bar(self, arg): return arg * 2 @@ -280,7 +288,7 @@ def bar(self, arg): @skip_pypy -def test_instance_method_by_subclass_spy(mocker): +def test_instance_method_by_subclass_spy(mocker: MockerFixture) -> None: class Base: def bar(self, arg): return arg * 2 @@ -299,7 +307,7 @@ class Foo(Base): @skip_pypy -def test_class_method_spy(mocker): +def test_class_method_spy(mocker: MockerFixture) -> None: class Foo: @classmethod def bar(cls, arg): @@ -307,14 +315,14 @@ def bar(cls, arg): spy = mocker.spy(Foo, "bar") assert Foo.bar(arg=10) == 20 - Foo.bar.assert_called_once_with(arg=10) - assert Foo.bar.spy_return == 20 + Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined] + assert Foo.bar.spy_return == 20 # type:ignore[attr-defined] spy.assert_called_once_with(arg=10) assert spy.spy_return == 20 @skip_pypy -def test_class_method_subclass_spy(mocker): +def test_class_method_subclass_spy(mocker: MockerFixture) -> None: class Base: @classmethod def bar(self, arg): @@ -325,14 +333,14 @@ class Foo(Base): spy = mocker.spy(Foo, "bar") assert Foo.bar(arg=10) == 20 - Foo.bar.assert_called_once_with(arg=10) - assert Foo.bar.spy_return == 20 + Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined] + assert Foo.bar.spy_return == 20 # type:ignore[attr-defined] spy.assert_called_once_with(arg=10) assert spy.spy_return == 20 @skip_pypy -def test_class_method_with_metaclass_spy(mocker): +def test_class_method_with_metaclass_spy(mocker: MockerFixture) -> None: class MetaFoo(type): pass @@ -346,14 +354,14 @@ def bar(cls, arg): spy = mocker.spy(Foo, "bar") assert Foo.bar(arg=10) == 20 - Foo.bar.assert_called_once_with(arg=10) - assert Foo.bar.spy_return == 20 + Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined] + assert Foo.bar.spy_return == 20 # type:ignore[attr-defined] spy.assert_called_once_with(arg=10) assert spy.spy_return == 20 @skip_pypy -def test_static_method_spy(mocker): +def test_static_method_spy(mocker: MockerFixture) -> None: class Foo: @staticmethod def bar(arg): @@ -361,14 +369,14 @@ def bar(arg): spy = mocker.spy(Foo, "bar") assert Foo.bar(arg=10) == 20 - Foo.bar.assert_called_once_with(arg=10) - assert Foo.bar.spy_return == 20 + Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined] + assert Foo.bar.spy_return == 20 # type:ignore[attr-defined] spy.assert_called_once_with(arg=10) assert spy.spy_return == 20 @skip_pypy -def test_static_method_subclass_spy(mocker): +def test_static_method_subclass_spy(mocker: MockerFixture) -> None: class Base: @staticmethod def bar(arg): @@ -379,13 +387,13 @@ class Foo(Base): spy = mocker.spy(Foo, "bar") assert Foo.bar(arg=10) == 20 - Foo.bar.assert_called_once_with(arg=10) - assert Foo.bar.spy_return == 20 + Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined] + assert Foo.bar.spy_return == 20 # type:ignore[attr-defined] spy.assert_called_once_with(arg=10) assert spy.spy_return == 20 -def test_callable_like_spy(testdir, mocker): +def test_callable_like_spy(testdir: Any, mocker: MockerFixture) -> None: testdir.makepyfile( uut=""" class CallLike(object): @@ -397,7 +405,7 @@ def __call__(self, x): ) testdir.syspathinsert() - import uut + uut = __import__("uut") spy = mocker.spy(uut, "call_like") uut.call_like(10) @@ -406,7 +414,7 @@ def __call__(self, x): @pytest.mark.asyncio -async def test_instance_async_method_spy(mocker): +async def test_instance_async_method_spy(mocker: MockerFixture) -> None: class Foo: async def bar(self, arg): return arg * 2 @@ -421,22 +429,20 @@ async def bar(self, arg): @contextmanager -def assert_traceback(): +def assert_traceback() -> Generator[None, None, None]: """ Assert that this file is at the top of the filtered traceback """ try: yield - except AssertionError: - traceback = py.code.ExceptionInfo().traceback - crashentry = traceback.getcrashentry() - assert crashentry.path == __file__ + except AssertionError as e: + assert e.__traceback__.tb_frame.f_code.co_filename == __file__ # type:ignore else: raise AssertionError("DID NOT RAISE") @contextmanager -def assert_argument_introspection(left, right): +def assert_argument_introspection(left: Any, right: Any) -> Generator[None, None, None]: """ Assert detailed argument introspection is used """ @@ -455,7 +461,7 @@ def assert_argument_introspection(left, right): raise AssertionError("DID NOT RAISE") -def test_assert_not_called_wrapper(mocker): +def test_assert_not_called_wrapper(mocker: MockerFixture) -> None: stub = mocker.stub() stub.assert_not_called() stub() @@ -463,7 +469,7 @@ def test_assert_not_called_wrapper(mocker): stub.assert_not_called() -def test_assert_called_with_wrapper(mocker): +def test_assert_called_with_wrapper(mocker: MockerFixture) -> None: stub = mocker.stub() stub("foo") stub.assert_called_with("foo") @@ -471,7 +477,7 @@ def test_assert_called_with_wrapper(mocker): stub.assert_called_with("bar") -def test_assert_called_once_with_wrapper(mocker): +def test_assert_called_once_with_wrapper(mocker: MockerFixture) -> None: stub = mocker.stub() stub("foo") stub.assert_called_once_with("foo") @@ -480,7 +486,7 @@ def test_assert_called_once_with_wrapper(mocker): stub.assert_called_once_with("foo") -def test_assert_called_once_wrapper(mocker): +def test_assert_called_once_wrapper(mocker: MockerFixture) -> None: stub = mocker.stub() if not hasattr(stub, "assert_called_once"): pytest.skip("assert_called_once not available") @@ -491,7 +497,7 @@ def test_assert_called_once_wrapper(mocker): stub.assert_called_once() -def test_assert_called_wrapper(mocker): +def test_assert_called_wrapper(mocker: MockerFixture) -> None: stub = mocker.stub() if not hasattr(stub, "assert_called"): pytest.skip("assert_called_once not available") @@ -504,7 +510,7 @@ def test_assert_called_wrapper(mocker): @pytest.mark.usefixtures("needs_assert_rewrite") -def test_assert_called_args_with_introspection(mocker): +def test_assert_called_args_with_introspection(mocker: MockerFixture) -> None: stub = mocker.stub() complex_args = ("a", 1, {"test"}) @@ -520,7 +526,7 @@ def test_assert_called_args_with_introspection(mocker): @pytest.mark.usefixtures("needs_assert_rewrite") -def test_assert_called_kwargs_with_introspection(mocker): +def test_assert_called_kwargs_with_introspection(mocker: MockerFixture) -> None: stub = mocker.stub() complex_kwargs = dict(foo={"bar": 1, "baz": "spam"}) @@ -535,7 +541,7 @@ def test_assert_called_kwargs_with_introspection(mocker): stub.assert_called_once_with(**wrong_kwargs) -def test_assert_any_call_wrapper(mocker): +def test_assert_any_call_wrapper(mocker: MockerFixture) -> None: stub = mocker.stub() stub("foo") stub("foo") @@ -544,7 +550,7 @@ def test_assert_any_call_wrapper(mocker): stub.assert_any_call("bar") -def test_assert_has_calls(mocker): +def test_assert_has_calls(mocker: MockerFixture) -> None: stub = mocker.stub() stub("foo") stub.assert_has_calls([mocker.call("foo")]) @@ -552,7 +558,7 @@ def test_assert_has_calls(mocker): stub.assert_has_calls([mocker.call("bar")]) -def test_monkeypatch_ini(mocker, testdir): +def test_monkeypatch_ini(testdir: Any, mocker: MockerFixture) -> None: # Make sure the following function actually tests something stub = mocker.stub() assert stub.assert_called_with.__module__ != stub.__module__ @@ -571,11 +577,11 @@ def test_foo(mocker): mock_traceback_monkeypatch = false """ ) - result = runpytest_subprocess(testdir) + result = testdir.runpytest_subprocess() assert result.ret == 0 -def test_parse_ini_boolean(): +def test_parse_ini_boolean() -> None: import pytest_mock assert pytest_mock.parse_ini_boolean("True") is True @@ -584,7 +590,7 @@ def test_parse_ini_boolean(): pytest_mock.parse_ini_boolean("foo") -def test_patched_method_parameter_name(mocker): +def test_patched_method_parameter_name(mocker: MockerFixture) -> None: """Test that our internal code uses uncommon names when wrapping other "mock" methods to avoid conflicts with user code (#31). """ @@ -599,7 +605,7 @@ def request(cls, method, args): m.assert_called_once_with(method="get", args={"type": "application/json"}) -def test_monkeypatch_native(testdir): +def test_monkeypatch_native(testdir: Any) -> None: """Automatically disable monkeypatching when --tb=native. """ testdir.makepyfile( @@ -610,7 +616,7 @@ def test_foo(mocker): stub.assert_called_once_with(1, greet='hey') """ ) - result = runpytest_subprocess(testdir, "--tb=native") + result = testdir.runpytest_subprocess("--tb=native") assert result.ret == 1 assert "During handling of the above exception" not in result.stdout.str() assert "Differing items:" not in result.stdout.str() @@ -624,7 +630,7 @@ def test_foo(mocker): ) # make sure there are no duplicated tracebacks (#44) -def test_monkeypatch_no_terminal(testdir): +def test_monkeypatch_no_terminal(testdir: Any) -> None: """Don't crash without 'terminal' plugin. """ testdir.makepyfile( @@ -635,12 +641,12 @@ def test_foo(mocker): stub.assert_called_once_with(1, greet='hey') """ ) - result = runpytest_subprocess(testdir, "-p", "no:terminal", "-s") + result = testdir.runpytest_subprocess("-p", "no:terminal", "-s") assert result.ret == 1 assert result.stdout.lines == [] -def test_standalone_mock(testdir): +def test_standalone_mock(testdir: Any) -> None: """Check that the "mock_use_standalone" is being used. """ testdir.makepyfile( @@ -655,22 +661,13 @@ def test_foo(mocker): mock_use_standalone_module = true """ ) - result = runpytest_subprocess(testdir) + result = testdir.runpytest_subprocess() assert result.ret == 3 result.stderr.fnmatch_lines(["*No module named 'mock'*"]) -def runpytest_subprocess(testdir, *args): - """Testdir.runpytest_subprocess only available in pytest-2.8+""" - if hasattr(testdir, "runpytest_subprocess"): - return testdir.runpytest_subprocess(*args) - else: - # pytest 2.7.X - return testdir.runpytest(*args) - - @pytest.mark.usefixtures("needs_assert_rewrite") -def test_detailed_introspection(testdir): +def test_detailed_introspection(testdir: Any) -> None: """Check that the "mock_use_standalone" is being used. """ testdir.makepyfile( @@ -712,7 +709,7 @@ def test(mocker): 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): +def test_detailed_introspection_async(testdir: Any) -> None: """Check that the "mock_use_standalone" is being used. """ testdir.makepyfile( @@ -745,7 +742,7 @@ async def test(mocker): result.stdout.fnmatch_lines(expected_lines) -def test_missing_introspection(testdir): +def test_missing_introspection(testdir: Any) -> None: testdir.makepyfile( """ def test_foo(mocker): @@ -759,7 +756,7 @@ def test_foo(mocker): assert "pytest introspection follows:" not in result.stdout.str() -def test_assert_called_with_unicode_arguments(mocker): +def test_assert_called_with_unicode_arguments(mocker: MockerFixture) -> None: """Test bug in assert_call_with called with non-ascii unicode string (#91)""" stub = mocker.stub() stub(b"l\xc3\xb6k".decode("UTF-8")) @@ -768,7 +765,7 @@ def test_assert_called_with_unicode_arguments(mocker): stub.assert_called_with("lak") -def test_plain_stopall(testdir): +def test_plain_stopall(testdir: Any) -> None: """patch.stopall() in a test should not cause an error during unconfigure (#137)""" testdir.makepyfile( """ @@ -789,7 +786,7 @@ def test_get_random_number(mocker): assert "RuntimeError" not in result.stderr.str() -def test_abort_patch_object_context_manager(mocker): +def test_abort_patch_object_context_manager(mocker: MockerFixture) -> None: class A: def doIt(self): return False @@ -808,7 +805,7 @@ def doIt(self): assert str(excinfo.value) == expected_error_msg -def test_abort_patch_context_manager(mocker): +def test_abort_patch_context_manager(mocker: MockerFixture) -> None: with pytest.raises(ValueError) as excinfo: with mocker.patch("json.loads"): pass @@ -821,7 +818,7 @@ def test_abort_patch_context_manager(mocker): assert str(excinfo.value) == expected_error_msg -def test_context_manager_patch_example(mocker): +def test_context_manager_patch_example(mocker: MockerFixture) -> None: """Our message about misusing mocker as a context manager should not affect mocking context managers (see #192)""" @@ -841,7 +838,7 @@ def my_func(): assert isinstance(my_func(), mocker.MagicMock) -def test_abort_patch_context_manager_with_stale_pyc(testdir): +def test_abort_patch_context_manager_with_stale_pyc(testdir: Any) -> None: """Ensure we don't trigger an error in case the frame where mocker.patch is being used doesn't have a 'context' (#169)""" import compileall @@ -868,8 +865,7 @@ def test_foo(mocker): result = testdir.runpytest() result.assert_outcomes(passed=1) - kwargs = {"legacy": True} - assert compileall.compile_file(str(py_fn), **kwargs) + assert compileall.compile_file(str(py_fn), legacy=True) pyc_fn = str(py_fn) + "c" assert os.path.isfile(pyc_fn) @@ -879,8 +875,7 @@ def test_foo(mocker): result.assert_outcomes(passed=1) -def test_used_with_class_scope(testdir): - """...""" +def test_used_with_class_scope(testdir: Any) -> None: testdir.makepyfile( """ import pytest @@ -904,8 +899,7 @@ def test_get_random_number(self): result.stdout.fnmatch_lines("* 1 passed in *") -def test_used_with_module_scope(testdir): - """...""" +def test_used_with_module_scope(testdir: Any) -> None: testdir.makepyfile( """ import pytest @@ -927,7 +921,7 @@ def test_get_random_number(): result.stdout.fnmatch_lines("* 1 passed in *") -def test_used_with_package_scope(testdir): +def test_used_with_package_scope(testdir: Any) -> None: """...""" testdir.makepyfile( """ @@ -950,7 +944,7 @@ def test_get_random_number(): result.stdout.fnmatch_lines("* 1 passed in *") -def test_used_with_session_scope(testdir): +def test_used_with_session_scope(testdir: Any) -> None: """...""" testdir.makepyfile( """ diff --git a/tox.ini b/tox.ini index e7ad7bb..66df940 100644 --- a/tox.ini +++ b/tox.ini @@ -15,16 +15,16 @@ commands = pytest tests --assert=plain [testenv:linting] -skipsdist = True usedevelop = True extras = dev basepython = python3.6 commands = pre-commit run --all-files --show-diff-on-failure [testenv:mypy] +skip_install = true deps = - mypy==0.780 -commands = mypy {posargs:src} + mypy==0.782 +commands = mypy {posargs:src tests} [pytest] addopts = -r a