aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/pytest-mock
diff options
context:
space:
mode:
authormaxim-yurchuk <maxim-yurchuk@yandex-team.com>2024-10-09 12:29:46 +0300
committermaxim-yurchuk <maxim-yurchuk@yandex-team.com>2024-10-09 13:14:22 +0300
commit9731d8a4bb7ee2cc8554eaf133bb85498a4c7d80 (patch)
treea8fb3181d5947c0d78cf402aa56e686130179049 /contrib/python/pytest-mock
parenta44b779cd359f06c3ebbef4ec98c6b38609d9d85 (diff)
downloadydb-9731d8a4bb7ee2cc8554eaf133bb85498a4c7d80.tar.gz
publishFullContrib: true for ydb
<HIDDEN_URL> commit_hash:c82a80ac4594723cebf2c7387dec9c60217f603e
Diffstat (limited to 'contrib/python/pytest-mock')
-rw-r--r--contrib/python/pytest-mock/py2/.yandex_meta/yamaker.yaml2
-rw-r--r--contrib/python/pytest-mock/py2/patches/01-fix-tests.patch90
-rw-r--r--contrib/python/pytest-mock/py2/patches/02-unknown.patch19
-rw-r--r--contrib/python/pytest-mock/py2/tests/test_pytest_mock.py825
-rw-r--r--contrib/python/pytest-mock/py2/tests/ya.make15
-rw-r--r--contrib/python/pytest-mock/py3/patches/01-fix-tests.patch76
-rw-r--r--contrib/python/pytest-mock/py3/tests/test_pytest_mock.py1307
-rw-r--r--contrib/python/pytest-mock/py3/tests/ya.make16
8 files changed, 2350 insertions, 0 deletions
diff --git a/contrib/python/pytest-mock/py2/.yandex_meta/yamaker.yaml b/contrib/python/pytest-mock/py2/.yandex_meta/yamaker.yaml
new file mode 100644
index 0000000000..a346c40f17
--- /dev/null
+++ b/contrib/python/pytest-mock/py2/.yandex_meta/yamaker.yaml
@@ -0,0 +1,2 @@
+requirements:
+ - six
diff --git a/contrib/python/pytest-mock/py2/patches/01-fix-tests.patch b/contrib/python/pytest-mock/py2/patches/01-fix-tests.patch
new file mode 100644
index 0000000000..ce41ab2227
--- /dev/null
+++ b/contrib/python/pytest-mock/py2/patches/01-fix-tests.patch
@@ -0,0 +1,90 @@
+--- contrib/python/pytest-mock/py2/tests/test_pytest_mock.py (index)
++++ contrib/python/pytest-mock/py2/tests/test_pytest_mock.py (working tree)
+@@ -387,6 +387,7 @@ def test_static_method_subclass_spy(mocker):
+ assert spy.spy_return == 20
+
+
++@pytest.mark.skip("Skip testdir")
+ def test_callable_like_spy(testdir, mocker):
+ testdir.makepyfile(
+ uut="""
+@@ -494,6 +495,7 @@ def test_assert_called_wrapper(mocker):
+ stub.assert_called()
+
+
++@pytest.mark.skip
+ @pytest.mark.usefixtures("needs_assert_rewrite")
+ def test_assert_called_args_with_introspection(mocker):
+ stub = mocker.stub()
+@@ -510,6 +512,7 @@ def test_assert_called_args_with_introspection(mocker):
+ stub.assert_called_once_with(*wrong_args)
+
+
++@pytest.mark.skip
+ @pytest.mark.usefixtures("needs_assert_rewrite")
+ def test_assert_called_kwargs_with_introspection(mocker):
+ stub = mocker.stub()
+@@ -543,6 +546,7 @@ def test_assert_has_calls(mocker):
+ stub.assert_has_calls([mocker.call("bar")])
+
+
++@pytest.mark.skip("Skip testdir")
+ def test_monkeypatch_ini(mocker, testdir):
+ # Make sure the following function actually tests something
+ stub = mocker.stub()
+@@ -590,6 +594,7 @@ def test_patched_method_parameter_name(mocker):
+ m.assert_called_once_with(method="get", args={"type": "application/json"})
+
+
++@pytest.mark.skip("Skip testdir")
+ def test_monkeypatch_native(testdir):
+ """Automatically disable monkeypatching when --tb=native.
+ """
+@@ -615,6 +620,7 @@ def test_monkeypatch_native(testdir):
+ ) # make sure there are no duplicated tracebacks (#44)
+
+
++@pytest.mark.skip("Skip testdir")
+ def test_monkeypatch_no_terminal(testdir):
+ """Don't crash without 'terminal' plugin.
+ """
+@@ -631,6 +637,7 @@ def test_monkeypatch_no_terminal(testdir):
+ assert result.stdout.lines == []
+
+
++@pytest.mark.skip("Skip testdir")
+ @pytest.mark.skipif(sys.version_info[0] < 3, reason="Py3 only")
+ def test_standalone_mock(testdir):
+ """Check that the "mock_use_standalone" is being used.
+@@ -661,6 +668,7 @@ def runpytest_subprocess(testdir, *args):
+ return testdir.runpytest(*args)
+
+
++@pytest.mark.skip("Skip testdir")
+ @pytest.mark.usefixtures("needs_assert_rewrite")
+ def test_detailed_introspection(testdir):
+ """Check that the "mock_use_standalone" is being used.
+@@ -700,6 +708,7 @@ def test_detailed_introspection(testdir):
+ result.stdout.fnmatch_lines(expected_lines)
+
+
++@pytest.mark.skip("Skip testdir")
+ def test_missing_introspection(testdir):
+ testdir.makepyfile(
+ """
+@@ -723,6 +732,7 @@ def test_assert_called_with_unicode_arguments(mocker):
+ stub.assert_called_with(u"lak")
+
+
++@pytest.mark.skip("Skip testdir")
+ def test_plain_stopall(testdir):
+ """patch.stopall() in a test should not cause an error during unconfigure (#137)"""
+ testdir.makepyfile(
+@@ -776,6 +786,7 @@ def test_abort_patch_context_manager(mocker):
+ assert str(excinfo.value) == expected_error_msg
+
+
++@pytest.mark.skip("Skip testdir")
+ def test_abort_patch_context_manager_with_stale_pyc(testdir):
+ """Ensure we don't trigger an error in case the frame where mocker.patch is being
+ used doesn't have a 'context' (#169)"""
diff --git a/contrib/python/pytest-mock/py2/patches/02-unknown.patch b/contrib/python/pytest-mock/py2/patches/02-unknown.patch
new file mode 100644
index 0000000000..c782dc1926
--- /dev/null
+++ b/contrib/python/pytest-mock/py2/patches/02-unknown.patch
@@ -0,0 +1,19 @@
+--- contrib/python/pytest-mock/py2/pytest_mock/plugin.py (index)
++++ contrib/python/pytest-mock/py2/pytest_mock/plugin.py (working tree)
+@@ -5,6 +5,7 @@ import inspect
+ import sys
+
+ import pytest
++import six
+
+ from ._version import version
+
+@@ -172,7 +173,7 @@ class MockFixture(object):
+ if info.code_context is None:
+ # no source code available (#169)
+ return
+- code_context = " ".join(info.code_context).strip()
++ code_context = " ".join(six.ensure_text(x) for x in info.code_context).strip()
+
+ if code_context.startswith("with mocker."):
+ raise ValueError(
diff --git a/contrib/python/pytest-mock/py2/tests/test_pytest_mock.py b/contrib/python/pytest-mock/py2/tests/test_pytest_mock.py
new file mode 100644
index 0000000000..7c3cf39e87
--- /dev/null
+++ b/contrib/python/pytest-mock/py2/tests/test_pytest_mock.py
@@ -0,0 +1,825 @@
+import os
+import platform
+import sys
+from contextlib import contextmanager
+
+import py.code
+import pytest
+
+pytest_plugins = "pytester"
+
+# could not make some of the tests work on PyPy, patches are welcome!
+skip_pypy = pytest.mark.skipif(
+ platform.python_implementation() == "PyPy", reason="could not make it work on pypy"
+)
+
+# Python 3.8 changed the output formatting (bpo-35500), which has been ported to mock 3.0
+NEW_FORMATTING = sys.version_info >= (3, 8) or sys.version_info[0] == 2
+
+
+@pytest.fixture
+def needs_assert_rewrite(pytestconfig):
+ """
+ Fixture which skips requesting test if assertion rewrite is disabled (#102)
+
+ Making this a fixture to avoid acessing pytest's config in the global context.
+ """
+ option = pytestconfig.getoption("assertmode")
+ if option != "rewrite":
+ pytest.skip(
+ "this test needs assertion rewrite to work but current option "
+ 'is "{}"'.format(option)
+ )
+
+
+class UnixFS(object):
+ """
+ Wrapper to os functions to simulate a Unix file system, used for testing
+ the mock fixture.
+ """
+
+ @classmethod
+ def rm(cls, filename):
+ os.remove(filename)
+
+ @classmethod
+ def ls(cls, path):
+ return os.listdir(path)
+
+
+@pytest.fixture
+def check_unix_fs_mocked(tmpdir, mocker):
+ """
+ performs a standard test in a UnixFS, assuming that both `os.remove` and
+ `os.listdir` have been mocked previously.
+ """
+
+ def check(mocked_rm, mocked_ls):
+ assert mocked_rm is os.remove
+ assert mocked_ls is os.listdir
+
+ file_name = tmpdir / "foo.txt"
+ file_name.ensure()
+
+ UnixFS.rm(str(file_name))
+ mocked_rm.assert_called_once_with(str(file_name))
+ assert os.path.isfile(str(file_name))
+
+ mocked_ls.return_value = ["bar.txt"]
+ assert UnixFS.ls(str(tmpdir)) == ["bar.txt"]
+ mocked_ls.assert_called_once_with(str(tmpdir))
+
+ mocker.stopall()
+
+ assert UnixFS.ls(str(tmpdir)) == ["foo.txt"]
+ UnixFS.rm(str(file_name))
+ assert not os.path.isfile(str(file_name))
+
+ return check
+
+
+def mock_using_patch_object(mocker):
+ return mocker.patch.object(os, "remove"), mocker.patch.object(os, "listdir")
+
+
+def mock_using_patch(mocker):
+ return mocker.patch("os.remove"), mocker.patch("os.listdir")
+
+
+def mock_using_patch_multiple(mocker):
+ r = mocker.patch.multiple("os", remove=mocker.DEFAULT, listdir=mocker.DEFAULT)
+ return r["remove"], r["listdir"]
+
+
+@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):
+ """
+ Installs mocks into `os` functions and performs a standard testing of
+ mock functionality. We parametrize different mock methods to ensure
+ all (intended, at least) mock API is covered.
+ """
+ # mock it twice on purpose to ensure we unmock it correctly later
+ mock_fs(mocker)
+ mocked_rm, mocked_ls = mock_fs(mocker)
+ check_unix_fs_mocked(mocked_rm, mocked_ls)
+ mocker.resetall()
+ mocker.stopall()
+
+
+def test_mock_patch_dict(mocker):
+ """
+ Testing
+ :param mock:
+ """
+ x = {"original": 1}
+ mocker.patch.dict(x, values=[("new", 10)], clear=True)
+ assert x == {"new": 10}
+ mocker.stopall()
+ assert x == {"original": 1}
+
+
+def test_mock_patch_dict_resetall(mocker):
+ """
+ We can call resetall after patching a dict.
+ :param mock:
+ """
+ x = {"original": 1}
+ mocker.patch.dict(x, values=[("new", 10)], clear=True)
+ assert x == {"new": 10}
+ mocker.resetall()
+ assert x == {"new": 10}
+
+
+@pytest.mark.parametrize(
+ "name",
+ [
+ "ANY",
+ "call",
+ "create_autospec",
+ "MagicMock",
+ "Mock",
+ "mock_open",
+ "NonCallableMock",
+ "PropertyMock",
+ "sentinel",
+ ],
+)
+def test_mocker_aliases(name, pytestconfig):
+ from pytest_mock import _get_mock_module, MockFixture
+
+ mock_module = _get_mock_module(pytestconfig)
+
+ mocker = MockFixture(pytestconfig)
+ assert getattr(mocker, name) is getattr(mock_module, name)
+
+
+def test_mocker_resetall(mocker):
+ listdir = mocker.patch("os.listdir")
+ open = mocker.patch("os.open")
+
+ listdir("/tmp")
+ open("/tmp/foo.txt")
+ listdir.assert_called_once_with("/tmp")
+ open.assert_called_once_with("/tmp/foo.txt")
+
+ mocker.resetall()
+
+ assert not listdir.called
+ assert not open.called
+
+
+class TestMockerStub:
+ def test_call(self, mocker):
+ stub = mocker.stub()
+ stub("foo", "bar")
+ stub.assert_called_once_with("foo", "bar")
+
+ def test_repr_with_no_name(self, mocker):
+ stub = mocker.stub()
+ assert "name" not in repr(stub)
+
+ def test_repr_with_name(self, mocker):
+ test_name = "funny walk"
+ stub = mocker.stub(name=test_name)
+ assert "name={0!r}".format(test_name) in repr(stub)
+
+ def __test_failure_message(self, mocker, **kwargs):
+ expected_name = kwargs.get("name") or "mock"
+ if NEW_FORMATTING:
+ msg = "expected call not found.\nExpected: {0}()\nActual: not called."
+ else:
+ msg = "Expected call: {0}()\nNot called"
+ expected_message = msg.format(expected_name)
+ stub = mocker.stub(**kwargs)
+ with pytest.raises(AssertionError) as exc_info:
+ stub.assert_called_with()
+ assert str(exc_info.value) == expected_message
+
+ def test_failure_message_with_no_name(self, mocker):
+ self.__test_failure_message(mocker)
+
+ @pytest.mark.parametrize("name", (None, "", "f", "The Castle of aaarrrrggh"))
+ def test_failure_message_with_name(self, mocker, name):
+ self.__test_failure_message(mocker, name=name)
+
+
+def test_instance_method_spy(mocker):
+ class Foo(object):
+ def bar(self, arg):
+ return arg * 2
+
+ foo = Foo()
+ other = Foo()
+ 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
+ spy.assert_called_once_with(arg=10)
+ assert spy.spy_return == 20
+
+
+def test_instance_method_spy_exception(mocker):
+ class Foo(object):
+ def bar(self, arg):
+ raise Exception("Error with {}".format(arg))
+
+ foo = Foo()
+ spy = mocker.spy(foo, "bar")
+
+ expected_calls = []
+ for i, v in enumerate([10, 20]):
+ with pytest.raises(Exception, match="Error with {}".format(v)) as exc_info:
+ foo.bar(arg=v)
+
+ expected_calls.append(mocker.call(arg=v))
+ assert foo.bar.call_args_list == expected_calls
+ assert str(spy.spy_exception) == "Error with {}".format(v)
+
+
+def test_spy_reset(mocker):
+ class Foo(object):
+ def bar(self, x):
+ if x == 0:
+ raise ValueError("invalid x")
+ return x * 3
+
+ spy = mocker.spy(Foo, "bar")
+ assert spy.spy_return is None
+ assert spy.spy_exception is None
+
+ Foo().bar(10)
+ assert spy.spy_return == 30
+ assert spy.spy_exception is None
+
+ with pytest.raises(ValueError):
+ Foo().bar(0)
+ assert spy.spy_return is None
+ assert str(spy.spy_exception) == "invalid x"
+
+ Foo().bar(15)
+ assert spy.spy_return == 45
+ assert spy.spy_exception is None
+
+
+@skip_pypy
+def test_instance_method_by_class_spy(mocker):
+ class Foo(object):
+ def bar(self, arg):
+ return arg * 2
+
+ spy = mocker.spy(Foo, "bar")
+ foo = Foo()
+ other = Foo()
+ assert foo.bar(arg=10) == 20
+ assert other.bar(arg=10) == 20
+ calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
+ assert spy.call_args_list == calls
+
+
+@skip_pypy
+def test_instance_method_by_subclass_spy(mocker):
+ class Base(object):
+ def bar(self, arg):
+ return arg * 2
+
+ class Foo(Base):
+ pass
+
+ spy = mocker.spy(Foo, "bar")
+ foo = Foo()
+ other = Foo()
+ assert foo.bar(arg=10) == 20
+ assert other.bar(arg=10) == 20
+ calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
+ assert spy.call_args_list == calls
+ assert spy.spy_return == 20
+
+
+@skip_pypy
+def test_class_method_spy(mocker):
+ class Foo(object):
+ @classmethod
+ def bar(cls, arg):
+ return arg * 2
+
+ 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
+ spy.assert_called_once_with(arg=10)
+ assert spy.spy_return == 20
+
+
+@skip_pypy
+@pytest.mark.xfail(sys.version_info[0] == 2, reason="does not work on Python 2")
+def test_class_method_subclass_spy(mocker):
+ class Base(object):
+ @classmethod
+ def bar(self, arg):
+ return arg * 2
+
+ class Foo(Base):
+ pass
+
+ 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
+ spy.assert_called_once_with(arg=10)
+ assert spy.spy_return == 20
+
+
+@skip_pypy
+def test_class_method_with_metaclass_spy(mocker):
+ class MetaFoo(type):
+ pass
+
+ class Foo(object):
+
+ __metaclass__ = MetaFoo
+
+ @classmethod
+ def bar(cls, arg):
+ return arg * 2
+
+ 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
+ spy.assert_called_once_with(arg=10)
+ assert spy.spy_return == 20
+
+
+@skip_pypy
+def test_static_method_spy(mocker):
+ class Foo(object):
+ @staticmethod
+ def bar(arg):
+ return arg * 2
+
+ 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
+ spy.assert_called_once_with(arg=10)
+ assert spy.spy_return == 20
+
+
+@skip_pypy
+@pytest.mark.xfail(sys.version_info[0] == 2, reason="does not work on Python 2")
+def test_static_method_subclass_spy(mocker):
+ class Base(object):
+ @staticmethod
+ def bar(arg):
+ return arg * 2
+
+ class Foo(Base):
+ pass
+
+ 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
+ spy.assert_called_once_with(arg=10)
+ assert spy.spy_return == 20
+
+
+@pytest.mark.skip("Skip testdir")
+def test_callable_like_spy(testdir, mocker):
+ testdir.makepyfile(
+ uut="""
+ class CallLike(object):
+ def __call__(self, x):
+ return x * 2
+
+ call_like = CallLike()
+ """
+ )
+ testdir.syspathinsert()
+
+ import uut
+
+ spy = mocker.spy(uut, "call_like")
+ uut.call_like(10)
+ spy.assert_called_once_with(10)
+ assert spy.spy_return == 20
+
+
+@contextmanager
+def assert_traceback():
+ """
+ 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__
+ else:
+ raise AssertionError("DID NOT RAISE")
+
+
+@contextmanager
+def assert_argument_introspection(left, right):
+ """
+ Assert detailed argument introspection is used
+ """
+ try:
+ yield
+ except AssertionError as e:
+ # this may be a bit too assuming, but seems nicer then hard-coding
+ import _pytest.assertion.util as util
+
+ # NOTE: we assert with either verbose or not, depending on how our own
+ # test was run by examining sys.argv
+ verbose = any(a.startswith("-v") for a in sys.argv)
+ expected = "\n ".join(util._compare_eq_iterable(left, right, verbose))
+ assert expected in str(e)
+ else:
+ raise AssertionError("DID NOT RAISE")
+
+
+@pytest.mark.skipif(
+ sys.version_info[:2] == (3, 4),
+ reason="assert_not_called not available in Python 3.4",
+)
+def test_assert_not_called_wrapper(mocker):
+ stub = mocker.stub()
+ stub.assert_not_called()
+ stub()
+ with assert_traceback():
+ stub.assert_not_called()
+
+
+def test_assert_called_with_wrapper(mocker):
+ stub = mocker.stub()
+ stub("foo")
+ stub.assert_called_with("foo")
+ with assert_traceback():
+ stub.assert_called_with("bar")
+
+
+def test_assert_called_once_with_wrapper(mocker):
+ stub = mocker.stub()
+ stub("foo")
+ stub.assert_called_once_with("foo")
+ stub("foo")
+ with assert_traceback():
+ stub.assert_called_once_with("foo")
+
+
+def test_assert_called_once_wrapper(mocker):
+ stub = mocker.stub()
+ if not hasattr(stub, "assert_called_once"):
+ pytest.skip("assert_called_once not available")
+ stub("foo")
+ stub.assert_called_once()
+ stub("foo")
+ with assert_traceback():
+ stub.assert_called_once()
+
+
+def test_assert_called_wrapper(mocker):
+ stub = mocker.stub()
+ if not hasattr(stub, "assert_called"):
+ pytest.skip("assert_called_once not available")
+ with assert_traceback():
+ stub.assert_called()
+ stub("foo")
+ stub.assert_called()
+ stub("foo")
+ stub.assert_called()
+
+
+@pytest.mark.skip
+@pytest.mark.usefixtures("needs_assert_rewrite")
+def test_assert_called_args_with_introspection(mocker):
+ stub = mocker.stub()
+
+ complex_args = ("a", 1, set(["test"]))
+ wrong_args = ("b", 2, set(["jest"]))
+
+ stub(*complex_args)
+ stub.assert_called_with(*complex_args)
+ stub.assert_called_once_with(*complex_args)
+
+ with assert_argument_introspection(complex_args, wrong_args):
+ stub.assert_called_with(*wrong_args)
+ stub.assert_called_once_with(*wrong_args)
+
+
+@pytest.mark.skip
+@pytest.mark.usefixtures("needs_assert_rewrite")
+def test_assert_called_kwargs_with_introspection(mocker):
+ stub = mocker.stub()
+
+ complex_kwargs = dict(foo={"bar": 1, "baz": "spam"})
+ wrong_kwargs = dict(foo={"goo": 1, "baz": "bran"})
+
+ stub(**complex_kwargs)
+ stub.assert_called_with(**complex_kwargs)
+ stub.assert_called_once_with(**complex_kwargs)
+
+ with assert_argument_introspection(complex_kwargs, wrong_kwargs):
+ stub.assert_called_with(**wrong_kwargs)
+ stub.assert_called_once_with(**wrong_kwargs)
+
+
+def test_assert_any_call_wrapper(mocker):
+ stub = mocker.stub()
+ stub("foo")
+ stub("foo")
+ stub.assert_any_call("foo")
+ with assert_traceback():
+ stub.assert_any_call("bar")
+
+
+def test_assert_has_calls(mocker):
+ stub = mocker.stub()
+ stub("foo")
+ stub.assert_has_calls([mocker.call("foo")])
+ with assert_traceback():
+ stub.assert_has_calls([mocker.call("bar")])
+
+
+@pytest.mark.skip("Skip testdir")
+def test_monkeypatch_ini(mocker, testdir):
+ # Make sure the following function actually tests something
+ stub = mocker.stub()
+ assert stub.assert_called_with.__module__ != stub.__module__
+
+ testdir.makepyfile(
+ """
+ import py.code
+ def test_foo(mocker):
+ stub = mocker.stub()
+ assert stub.assert_called_with.__module__ == stub.__module__
+ """
+ )
+ testdir.makeini(
+ """
+ [pytest]
+ mock_traceback_monkeypatch = false
+ """
+ )
+ result = runpytest_subprocess(testdir)
+ assert result.ret == 0
+
+
+def test_parse_ini_boolean():
+ import pytest_mock
+
+ assert pytest_mock.parse_ini_boolean("True") is True
+ assert pytest_mock.parse_ini_boolean("false") is False
+ with pytest.raises(ValueError):
+ pytest_mock.parse_ini_boolean("foo")
+
+
+def test_patched_method_parameter_name(mocker):
+ """Test that our internal code uses uncommon names when wrapping other
+ "mock" methods to avoid conflicts with user code (#31).
+ """
+
+ class Request:
+ @classmethod
+ def request(cls, method, args):
+ pass
+
+ m = mocker.patch.object(Request, "request")
+ Request.request(method="get", args={"type": "application/json"})
+ m.assert_called_once_with(method="get", args={"type": "application/json"})
+
+
+@pytest.mark.skip("Skip testdir")
+def test_monkeypatch_native(testdir):
+ """Automatically disable monkeypatching when --tb=native.
+ """
+ testdir.makepyfile(
+ """
+ def test_foo(mocker):
+ stub = mocker.stub()
+ stub(1, greet='hello')
+ stub.assert_called_once_with(1, greet='hey')
+ """
+ )
+ result = runpytest_subprocess(testdir, "--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()
+ traceback_lines = [
+ x
+ for x in result.stdout.str().splitlines()
+ if "Traceback (most recent call last)" in x
+ ]
+ assert (
+ len(traceback_lines) == 1
+ ) # make sure there are no duplicated tracebacks (#44)
+
+
+@pytest.mark.skip("Skip testdir")
+def test_monkeypatch_no_terminal(testdir):
+ """Don't crash without 'terminal' plugin.
+ """
+ testdir.makepyfile(
+ """
+ def test_foo(mocker):
+ stub = mocker.stub()
+ stub(1, greet='hello')
+ stub.assert_called_once_with(1, greet='hey')
+ """
+ )
+ result = runpytest_subprocess(testdir, "-p", "no:terminal", "-s")
+ assert result.ret == 1
+ assert result.stdout.lines == []
+
+
+@pytest.mark.skip("Skip testdir")
+@pytest.mark.skipif(sys.version_info[0] < 3, reason="Py3 only")
+def test_standalone_mock(testdir):
+ """Check that the "mock_use_standalone" is being used.
+ """
+ testdir.makepyfile(
+ """
+ def test_foo(mocker):
+ pass
+ """
+ )
+ testdir.makeini(
+ """
+ [pytest]
+ mock_use_standalone_module = true
+ """
+ )
+ result = runpytest_subprocess(testdir)
+ 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.skip("Skip testdir")
+@pytest.mark.usefixtures("needs_assert_rewrite")
+def test_detailed_introspection(testdir):
+ """Check that the "mock_use_standalone" is being used.
+ """
+ testdir.makepyfile(
+ """
+ def test(mocker):
+ m = mocker.Mock()
+ m('fo')
+ m.assert_called_once_with('', bar=4)
+ """
+ )
+ result = testdir.runpytest("-s")
+ if NEW_FORMATTING:
+ expected_lines = [
+ "*AssertionError: expected call not found.",
+ "*Expected: mock('', bar=4)",
+ "*Actual: mock('fo')",
+ ]
+ else:
+ expected_lines = [
+ "*AssertionError: Expected call: mock('', bar=4)*",
+ "*Actual call: mock('fo')*",
+ ]
+ expected_lines += [
+ "*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)
+
+
+@pytest.mark.skip("Skip testdir")
+def test_missing_introspection(testdir):
+ testdir.makepyfile(
+ """
+ def test_foo(mocker):
+ mock = mocker.Mock()
+ mock('foo')
+ mock('test')
+ mock.assert_called_once_with('test')
+ """
+ )
+ result = testdir.runpytest()
+ assert "pytest introspection follows:" not in result.stdout.str()
+
+
+def test_assert_called_with_unicode_arguments(mocker):
+ """Test bug in assert_call_with called with non-ascii unicode string (#91)"""
+ stub = mocker.stub()
+ stub(b"l\xc3\xb6k".decode("UTF-8"))
+
+ with pytest.raises(AssertionError):
+ stub.assert_called_with(u"lak")
+
+
+@pytest.mark.skip("Skip testdir")
+def test_plain_stopall(testdir):
+ """patch.stopall() in a test should not cause an error during unconfigure (#137)"""
+ testdir.makepyfile(
+ """
+ import random
+
+ def get_random_number():
+ return random.randint(0, 100)
+
+ def test_get_random_number(mocker):
+ patcher = mocker.mock_module.patch("random.randint", lambda x, y: 5)
+ patcher.start()
+ assert get_random_number() == 5
+ mocker.mock_module.patch.stopall()
+ """
+ )
+ result = testdir.runpytest_subprocess()
+ result.stdout.fnmatch_lines("* 1 passed in *")
+ assert "RuntimeError" not in result.stderr.str()
+
+
+def test_abort_patch_object_context_manager(mocker):
+ class A(object):
+ def doIt(self):
+ return False
+
+ a = A()
+
+ with pytest.raises(ValueError) as excinfo:
+ with mocker.patch.object(a, "doIt", return_value=True):
+ assert a.doIt() == True
+
+ expected_error_msg = (
+ "Using mocker in a with context is not supported. "
+ "https://github.com/pytest-dev/pytest-mock#note-about-usage-as-context-manager"
+ )
+
+ assert str(excinfo.value) == expected_error_msg
+
+
+def test_abort_patch_context_manager(mocker):
+ with pytest.raises(ValueError) as excinfo:
+ with mocker.patch("some_package"):
+ pass
+
+ expected_error_msg = (
+ "Using mocker in a with context is not supported. "
+ "https://github.com/pytest-dev/pytest-mock#note-about-usage-as-context-manager"
+ )
+
+ assert str(excinfo.value) == expected_error_msg
+
+
+@pytest.mark.skip("Skip testdir")
+def test_abort_patch_context_manager_with_stale_pyc(testdir):
+ """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
+
+ py_fn = testdir.makepyfile(
+ c="""
+ class C:
+ x = 1
+
+ def check(mocker):
+ mocker.patch.object(C, "x", 2)
+ assert C.x == 2
+ """
+ )
+ testdir.syspathinsert()
+
+ testdir.makepyfile(
+ """
+ from c import check
+ def test_foo(mocker):
+ check(mocker)
+ """
+ )
+ result = testdir.runpytest()
+ result.stdout.fnmatch_lines("* 1 passed *")
+
+ kwargs = {"legacy": True} if sys.version_info[0] >= 3 else {}
+ assert compileall.compile_file(str(py_fn), **kwargs)
+
+ pyc_fn = str(py_fn) + "c"
+ assert os.path.isfile(pyc_fn)
+
+ py_fn.remove()
+ result = testdir.runpytest()
+ result.stdout.fnmatch_lines("* 1 passed *")
diff --git a/contrib/python/pytest-mock/py2/tests/ya.make b/contrib/python/pytest-mock/py2/tests/ya.make
new file mode 100644
index 0000000000..c495998e96
--- /dev/null
+++ b/contrib/python/pytest-mock/py2/tests/ya.make
@@ -0,0 +1,15 @@
+PY2TEST()
+
+SUBSCRIBER(g:python-contrib)
+
+PEERDIR(
+ contrib/python/pytest-mock/py2
+)
+
+TEST_SRCS(
+ test_pytest_mock.py
+)
+
+NO_LINT()
+
+END()
diff --git a/contrib/python/pytest-mock/py3/patches/01-fix-tests.patch b/contrib/python/pytest-mock/py3/patches/01-fix-tests.patch
new file mode 100644
index 0000000000..80864944ae
--- /dev/null
+++ b/contrib/python/pytest-mock/py3/patches/01-fix-tests.patch
@@ -0,0 +1,76 @@
+--- contrib/python/pytest-mock/py3/tests/test_pytest_mock.py (index)
++++ contrib/python/pytest-mock/py3/tests/test_pytest_mock.py (working tree)
+@@ -469,2 +469,3 @@ def test_static_method_subclass_spy(mocker: MockerFixture) -> None:
+
++@pytest.mark.skip("Skip testdir")
+ def test_callable_like_spy(testdir: Any, mocker: MockerFixture) -> None:
+@@ -583,4 +585,5 @@ def test_assert_called_wrapper(mocker: MockerFixture) -> None:
+
+
++@pytest.mark.skip
+ @pytest.mark.usefixtures("needs_assert_rewrite")
+ def test_assert_called_args_with_introspection(mocker: MockerFixture) -> None:
+@@ -599,4 +602,5 @@ def test_assert_called_args_with_introspection(mocker: MockerFixture) -> None:
+
+
++@pytest.mark.skip
+ @pytest.mark.usefixtures("needs_assert_rewrite")
+ def test_assert_called_kwargs_with_introspection(mocker: MockerFixture) -> None:
+@@ -633,2 +637,3 @@ def test_assert_has_calls(mocker: MockerFixture) -> None:
+
++@pytest.mark.skip("Skip testdir")
+ def test_monkeypatch_ini(testdir: Any, mocker: MockerFixture) -> None:
+@@ -680,2 +685,3 @@ def test_patched_method_parameter_name(mocker: MockerFixture) -> None:
+
++@pytest.mark.skip("Skip testdir")
+ def test_monkeypatch_native(testdir: Any) -> None:
+@@ -704,2 +710,3 @@ def test_monkeypatch_native(testdir: Any) -> None:
+
++@pytest.mark.skip("Skip testdir")
+ def test_monkeypatch_no_terminal(testdir: Any) -> None:
+@@ -719,2 +725,3 @@ def test_monkeypatch_no_terminal(testdir: Any) -> None:
+
++@pytest.mark.skip("Skip testdir")
+ def test_standalone_mock(testdir: Any) -> None:
+@@ -740,4 +748,5 @@ def test_standalone_mock(testdir: Any) -> None:
+
+
++@pytest.mark.skip("Skip testdir")
+ @pytest.mark.usefixtures("needs_assert_rewrite")
+ def test_detailed_introspection(testdir: Any) -> None:
+@@ -783,6 +791,7 @@ def test_detailed_introspection(testdir: Any) -> None:
+ result.stdout.fnmatch_lines(expected_lines)
+
+
++@pytest.mark.skip("Skip testdir")
+ @pytest.mark.usefixtures("needs_assert_rewrite")
+ def test_detailed_introspection_async(testdir: Any) -> None:
+ """Check that the "mock_use_standalone" is being used."""
+@@ -826,2 +836,3 @@ def test_detailed_introspection_async(testdir: Any) -> None:
+
++@pytest.mark.skip("Skip testdir")
+ def test_missing_introspection(testdir: Any) -> None:
+@@ -847,2 +859,3 @@ def test_assert_called_with_unicode_arguments(mocker: MockerFixture) -> None:
+
++@pytest.mark.skip("Skip testdir")
+ def test_plain_stopall(testdir: Any) -> None:
+@@ -953,2 +964,3 @@ def test_patch_context_manager_with_context_manager(mocker: MockerFixture) -> No
+
++@pytest.mark.skip("Skip testdir")
+ def test_abort_patch_context_manager_with_stale_pyc(testdir: Any) -> None:
+@@ -957,2 +970,3 @@ def test_abort_patch_context_manager_with_stale_pyc(testdir: Any) -> None:
+
++@pytest.mark.skip("Skip testdir")
+ def test_used_with_class_scope(testdir: Any) -> None:
+@@ -981,2 +995,3 @@ def test_used_with_class_scope(testdir: Any) -> None:
+
++@pytest.mark.skip("Skip testdir")
+ def test_used_with_module_scope(testdir: Any) -> None:
+@@ -1003,2 +1018,3 @@ def test_used_with_module_scope(testdir: Any) -> None:
+
++@pytest.mark.skip("Skip testdir")
+ def test_used_with_package_scope(testdir: Any) -> None:
+@@ -1026,2 +1042,3 @@ def test_used_with_package_scope(testdir: Any) -> None:
+
++@pytest.mark.skip("Skip testdir")
+ def test_used_with_session_scope(testdir: Any) -> None:
diff --git a/contrib/python/pytest-mock/py3/tests/test_pytest_mock.py b/contrib/python/pytest-mock/py3/tests/test_pytest_mock.py
new file mode 100644
index 0000000000..7831ddc300
--- /dev/null
+++ b/contrib/python/pytest-mock/py3/tests/test_pytest_mock.py
@@ -0,0 +1,1307 @@
+import os
+import platform
+import re
+import sys
+import warnings
+from contextlib import contextmanager
+from typing import Any
+from typing import Callable
+from typing import Generator
+from typing import Tuple
+from typing import Type
+from unittest.mock import AsyncMock
+from unittest.mock import MagicMock
+
+import pytest
+from pytest_mock import MockerFixture
+from pytest_mock import PytestMockWarning
+
+pytest_plugins = "pytester"
+
+# could not make some of the tests work on PyPy, patches are welcome!
+skip_pypy = pytest.mark.skipif(
+ platform.python_implementation() == "PyPy", reason="could not make it work on pypy"
+)
+
+# Python 3.11.7 changed the output formatting, https://github.com/python/cpython/issues/111019
+NEWEST_FORMATTING = sys.version_info >= (3, 11, 7)
+
+
+@pytest.fixture
+def needs_assert_rewrite(pytestconfig):
+ """
+ Fixture which skips requesting test if assertion rewrite is disabled (#102)
+
+ Making this a fixture to avoid accessing pytest's config in the global context.
+ """
+ option = pytestconfig.getoption("assertmode")
+ if option != "rewrite":
+ pytest.skip(
+ "this test needs assertion rewrite to work but current option "
+ 'is "{}"'.format(option)
+ )
+
+
+class UnixFS:
+ """
+ Wrapper to os functions to simulate a Unix file system, used for testing
+ the mock fixture.
+ """
+
+ @classmethod
+ def rm(cls, filename):
+ os.remove(filename)
+
+ @classmethod
+ def ls(cls, path):
+ return os.listdir(path)
+
+
+class TestObject:
+ """
+ Class that is used for testing create_autospec with child mocks
+ """
+
+ def run(self) -> str:
+ return "not mocked"
+
+
+@pytest.fixture
+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.
+ """
+
+ def check(mocked_rm, mocked_ls):
+ assert mocked_rm is os.remove
+ assert mocked_ls is os.listdir
+
+ file_name = tmpdir / "foo.txt"
+ file_name.ensure()
+
+ UnixFS.rm(str(file_name))
+ mocked_rm.assert_called_once_with(str(file_name))
+ assert os.path.isfile(str(file_name))
+
+ mocked_ls.return_value = ["bar.txt"]
+ assert UnixFS.ls(str(tmpdir)) == ["bar.txt"]
+ mocked_ls.assert_called_once_with(str(tmpdir))
+
+ mocker.stopall()
+
+ assert UnixFS.ls(str(tmpdir)) == ["foo.txt"]
+ UnixFS.rm(str(file_name))
+ assert not os.path.isfile(str(file_name))
+
+ return check
+
+
+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: MockerFixture) -> Tuple[MagicMock, MagicMock]:
+ return mocker.patch("os.remove"), mocker.patch("os.listdir")
+
+
+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"]
+
+
+@pytest.mark.parametrize(
+ "mock_fs", [mock_using_patch_object, mock_using_patch, mock_using_patch_multiple]
+)
+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
+ all (intended, at least) mock API is covered.
+ """
+ # mock it twice on purpose to ensure we unmock it correctly later
+ mock_fs(mocker)
+ mocked_rm, mocked_ls = mock_fs(mocker)
+ check_unix_fs_mocked(mocked_rm, mocked_ls)
+ mocker.resetall()
+ mocker.stopall()
+
+
+def test_mock_patch_dict(mocker: MockerFixture) -> None:
+ """
+ Testing
+ :param mock:
+ """
+ x = {"original": 1}
+ mocker.patch.dict(x, values=[("new", 10)], clear=True)
+ assert x == {"new": 10}
+ mocker.stopall()
+ assert x == {"original": 1}
+
+
+def test_mock_patch_dict_resetall(mocker: MockerFixture) -> None:
+ """
+ We can call resetall after patching a dict.
+ :param mock:
+ """
+ x = {"original": 1}
+ mocker.patch.dict(x, values=[("new", 10)], clear=True)
+ assert x == {"new": 10}
+ mocker.resetall()
+ assert x == {"new": 10}
+
+
+@pytest.mark.parametrize(
+ "name",
+ [
+ "ANY",
+ "call",
+ "MagicMock",
+ "Mock",
+ "mock_open",
+ "NonCallableMagicMock",
+ "NonCallableMock",
+ "PropertyMock",
+ "sentinel",
+ "seal",
+ ],
+)
+def test_mocker_aliases(name: str, pytestconfig: Any) -> None:
+ from pytest_mock._util import get_mock_module
+
+ mock_module = get_mock_module(pytestconfig)
+
+ mocker = MockerFixture(pytestconfig)
+ assert getattr(mocker, name) is getattr(mock_module, name)
+
+
+def test_mocker_resetall(mocker: MockerFixture) -> None:
+ listdir = mocker.patch("os.listdir", return_value="foo")
+ open = mocker.patch("os.open", side_effect=["bar", "baz"])
+
+ mocked_object = mocker.create_autospec(TestObject)
+ mocked_object.run.return_value = "mocked"
+
+ assert listdir("/tmp") == "foo"
+ assert open("/tmp/foo.txt") == "bar"
+ assert mocked_object.run() == "mocked"
+ listdir.assert_called_once_with("/tmp")
+ open.assert_called_once_with("/tmp/foo.txt")
+ mocked_object.run.assert_called_once()
+
+ mocker.resetall()
+
+ assert not listdir.called
+ assert not open.called
+ assert not mocked_object.called
+ assert listdir.return_value == "foo"
+ assert list(open.side_effect) == ["baz"]
+ assert mocked_object.run.return_value == "mocked"
+
+ mocker.resetall(return_value=True, side_effect=True)
+
+ assert isinstance(listdir.return_value, mocker.Mock)
+ assert open.side_effect is None
+
+ if sys.version_info >= (3, 9):
+ # The reset on child mocks have been implemented in 3.9
+ # https://bugs.python.org/issue38932
+ assert mocked_object.run.return_value != "mocked"
+
+
+class TestMockerStub:
+ 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: MockerFixture) -> None:
+ stub = mocker.stub()
+ assert "name" not in repr(stub)
+
+ def test_repr_with_name(self, mocker: MockerFixture) -> None:
+ test_name = "funny walk"
+ stub = mocker.stub(name=test_name)
+ assert f"name={test_name!r}" in repr(stub)
+
+ def __test_failure_message(self, mocker: MockerFixture, **kwargs: Any) -> None:
+ expected_name = kwargs.get("name") or "mock"
+ if NEWEST_FORMATTING:
+ msg = "expected call not found.\nExpected: {0}()\n Actual: not called."
+ else:
+ msg = "expected call not found.\nExpected: {0}()\nActual: not called."
+ expected_message = msg.format(expected_name)
+ stub = mocker.stub(**kwargs)
+ with pytest.raises(AssertionError, match=re.escape(expected_message)):
+ stub.assert_called_with()
+
+ 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: MagicMock, name: str) -> None:
+ self.__test_failure_message(mocker, name=name)
+
+ def test_async_stub_type(self, mocker: MockerFixture) -> None:
+ assert isinstance(mocker.async_stub(), AsyncMock)
+
+
+def test_instance_method_spy(mocker: MockerFixture) -> None:
+ class Foo:
+ def bar(self, arg):
+ return arg * 2
+
+ foo = Foo()
+ other = Foo()
+ 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) # type:ignore[attr-defined]
+ assert foo.bar.spy_return == 20 # type:ignore[attr-defined]
+ assert foo.bar.spy_return_list == [20] # type:ignore[attr-defined]
+ spy.assert_called_once_with(arg=10)
+ assert spy.spy_return == 20
+ assert foo.bar(arg=11) == 22
+ assert foo.bar(arg=12) == 24
+ assert spy.spy_return == 24
+ assert spy.spy_return_list == [20, 22, 24]
+
+
+# Ref: https://docs.python.org/3/library/exceptions.html#exception-hierarchy
+@pytest.mark.parametrize(
+ "exc_cls",
+ (
+ BaseException,
+ Exception,
+ GeneratorExit, # BaseException
+ KeyboardInterrupt, # BaseException
+ RuntimeError, # regular Exception
+ SystemExit, # BaseException
+ ),
+)
+def test_instance_method_spy_exception(
+ exc_cls: Type[BaseException],
+ mocker: MockerFixture,
+) -> None:
+ class Foo:
+ def bar(self, arg):
+ raise exc_cls(f"Error with {arg}")
+
+ foo = Foo()
+ spy = mocker.spy(foo, "bar")
+
+ expected_calls = []
+ for i, v in enumerate([10, 20]):
+ with pytest.raises(exc_cls, match=f"Error with {v}"):
+ foo.bar(arg=v)
+
+ expected_calls.append(mocker.call(arg=v))
+ assert foo.bar.call_args_list == expected_calls # type:ignore[attr-defined]
+ assert str(spy.spy_exception) == f"Error with {v}"
+
+
+def test_instance_class_static_method_spy_autospec_true(mocker: MockerFixture) -> None:
+ class Foo:
+ def bar(self, arg):
+ return arg * 2
+
+ @classmethod
+ def baz(cls, arg):
+ return arg * 2
+
+ @staticmethod
+ def qux(arg):
+ return arg * 2
+
+ foo = Foo()
+ instance_method_spy = mocker.spy(foo, "bar")
+ with pytest.raises(
+ AttributeError, match="'function' object has no attribute 'fake_assert_method'"
+ ):
+ instance_method_spy.fake_assert_method(arg=5)
+
+ class_method_spy = mocker.spy(Foo, "baz")
+ with pytest.raises(
+ AttributeError, match="Mock object has no attribute 'fake_assert_method'"
+ ):
+ class_method_spy.fake_assert_method(arg=5)
+
+ static_method_spy = mocker.spy(Foo, "qux")
+ with pytest.raises(
+ AttributeError, match="Mock object has no attribute 'fake_assert_method'"
+ ):
+ static_method_spy.fake_assert_method(arg=5)
+
+
+def test_spy_reset(mocker: MockerFixture) -> None:
+ class Foo:
+ def bar(self, x):
+ if x == 0:
+ raise ValueError("invalid x")
+ return x * 3
+
+ spy = mocker.spy(Foo, "bar")
+ assert spy.spy_return is None
+ assert spy.spy_return_list == []
+ assert spy.spy_exception is None
+
+ Foo().bar(10)
+ assert spy.spy_return == 30
+ assert spy.spy_return_list == [30]
+ assert spy.spy_exception is None
+
+ # Testing spy can still be reset (#237).
+ mocker.resetall()
+
+ with pytest.raises(ValueError):
+ Foo().bar(0)
+ assert spy.spy_return is None
+ assert spy.spy_return_list == []
+ assert str(spy.spy_exception) == "invalid x"
+
+ Foo().bar(15)
+ assert spy.spy_return == 45
+ assert spy.spy_return_list == [45]
+ assert spy.spy_exception is None
+
+
+@skip_pypy
+def test_instance_method_by_class_spy(mocker: MockerFixture) -> None:
+ class Foo:
+ def bar(self, arg):
+ return arg * 2
+
+ spy = mocker.spy(Foo, "bar")
+ foo = Foo()
+ other = Foo()
+ assert foo.bar(arg=10) == 20
+ assert other.bar(arg=10) == 20
+ calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
+ assert spy.call_args_list == calls
+
+
+@skip_pypy
+def test_instance_method_by_subclass_spy(mocker: MockerFixture) -> None:
+ class Base:
+ def bar(self, arg):
+ return arg * 2
+
+ class Foo(Base):
+ pass
+
+ spy = mocker.spy(Foo, "bar")
+ foo = Foo()
+ other = Foo()
+ assert foo.bar(arg=10) == 20
+ assert other.bar(arg=10) == 20
+ calls = [mocker.call(foo, arg=10), mocker.call(other, arg=10)]
+ assert spy.call_args_list == calls
+ assert spy.spy_return == 20
+ assert spy.spy_return_list == [20, 20]
+
+
+@skip_pypy
+def test_class_method_spy(mocker: MockerFixture) -> None:
+ class Foo:
+ @classmethod
+ def bar(cls, arg):
+ return arg * 2
+
+ spy = mocker.spy(Foo, "bar")
+ assert Foo.bar(arg=10) == 20
+ Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined]
+ assert Foo.bar.spy_return == 20 # type:ignore[attr-defined]
+ assert Foo.bar.spy_return_list == [20] # type:ignore[attr-defined]
+ spy.assert_called_once_with(arg=10)
+ assert spy.spy_return == 20
+ assert spy.spy_return_list == [20]
+
+
+@skip_pypy
+def test_class_method_subclass_spy(mocker: MockerFixture) -> None:
+ class Base:
+ @classmethod
+ def bar(self, arg):
+ return arg * 2
+
+ class Foo(Base):
+ pass
+
+ spy = mocker.spy(Foo, "bar")
+ assert Foo.bar(arg=10) == 20
+ Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined]
+ assert Foo.bar.spy_return == 20 # type:ignore[attr-defined]
+ assert Foo.bar.spy_return_list == [20] # type:ignore[attr-defined]
+ spy.assert_called_once_with(arg=10)
+ assert spy.spy_return == 20
+ assert spy.spy_return_list == [20]
+
+
+@skip_pypy
+def test_class_method_with_metaclass_spy(mocker: MockerFixture) -> None:
+ class MetaFoo(type):
+ pass
+
+ class Foo:
+ __metaclass__ = MetaFoo
+
+ @classmethod
+ def bar(cls, arg):
+ return arg * 2
+
+ spy = mocker.spy(Foo, "bar")
+ assert Foo.bar(arg=10) == 20
+ Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined]
+ assert Foo.bar.spy_return == 20 # type:ignore[attr-defined]
+ assert Foo.bar.spy_return_list == [20] # type:ignore[attr-defined]
+ spy.assert_called_once_with(arg=10)
+ assert spy.spy_return == 20
+ assert spy.spy_return_list == [20]
+
+
+@skip_pypy
+def test_static_method_spy(mocker: MockerFixture) -> None:
+ class Foo:
+ @staticmethod
+ def bar(arg):
+ return arg * 2
+
+ spy = mocker.spy(Foo, "bar")
+ assert Foo.bar(arg=10) == 20
+ Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined]
+ assert Foo.bar.spy_return == 20 # type:ignore[attr-defined]
+ assert Foo.bar.spy_return_list == [20] # type:ignore[attr-defined]
+ spy.assert_called_once_with(arg=10)
+ assert spy.spy_return == 20
+ assert spy.spy_return_list == [20]
+
+
+@skip_pypy
+def test_static_method_subclass_spy(mocker: MockerFixture) -> None:
+ class Base:
+ @staticmethod
+ def bar(arg):
+ return arg * 2
+
+ class Foo(Base):
+ pass
+
+ spy = mocker.spy(Foo, "bar")
+ assert Foo.bar(arg=10) == 20
+ Foo.bar.assert_called_once_with(arg=10) # type:ignore[attr-defined]
+ assert Foo.bar.spy_return == 20 # type:ignore[attr-defined]
+ assert Foo.bar.spy_return_list == [20] # type:ignore[attr-defined]
+ spy.assert_called_once_with(arg=10)
+ assert spy.spy_return == 20
+ assert spy.spy_return_list == [20]
+
+
+@pytest.mark.skip("Skip testdir")
+def test_callable_like_spy(testdir: Any, mocker: MockerFixture) -> None:
+ testdir.makepyfile(
+ uut="""
+ class CallLike(object):
+ def __call__(self, x):
+ return x * 2
+
+ call_like = CallLike()
+ """
+ )
+ testdir.syspathinsert()
+
+ uut = __import__("uut")
+
+ spy = mocker.spy(uut, "call_like")
+ uut.call_like(10)
+ spy.assert_called_once_with(10)
+ assert spy.spy_return == 20
+ assert spy.spy_return_list == [20]
+
+
+async def test_instance_async_method_spy(mocker: MockerFixture) -> None:
+ class Foo:
+ async def bar(self, arg):
+ return arg * 2
+
+ foo = Foo()
+ spy = mocker.spy(foo, "bar")
+
+ result = await foo.bar(10)
+
+ spy.assert_called_once_with(10)
+ assert result == 20
+
+
+@contextmanager
+def assert_traceback() -> Generator[None, None, None]:
+ """
+ Assert that this file is at the top of the filtered traceback
+ """
+ try:
+ yield
+ 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: Any, right: Any) -> Generator[None, None, None]:
+ """
+ Assert detailed argument introspection is used
+ """
+ try:
+ yield
+ except AssertionError as e:
+ # this may be a bit too assuming, but seems nicer then hard-coding
+ import _pytest.assertion.util as util
+
+ # NOTE: we assert with either verbose or not, depending on how our own
+ # test was run by examining sys.argv
+ verbose = any(a.startswith("-v") for a in sys.argv)
+ if int(pytest.__version__.split(".")[0]) < 8:
+ expected = "\n ".join(util._compare_eq_iterable(left, right, verbose)) # type:ignore[arg-type]
+ else:
+ expected = "\n ".join(
+ util._compare_eq_iterable(left, right, lambda t, *_: t, verbose)
+ )
+ assert expected in str(e)
+ else:
+ raise AssertionError("DID NOT RAISE")
+
+
+def test_assert_not_called_wrapper(mocker: MockerFixture) -> None:
+ stub = mocker.stub()
+ stub.assert_not_called()
+ stub()
+ with assert_traceback():
+ stub.assert_not_called()
+
+
+def test_assert_called_with_wrapper(mocker: MockerFixture) -> None:
+ stub = mocker.stub()
+ stub("foo")
+ stub.assert_called_with("foo")
+ with assert_traceback():
+ stub.assert_called_with("bar")
+
+
+def test_assert_called_once_with_wrapper(mocker: MockerFixture) -> None:
+ stub = mocker.stub()
+ stub("foo")
+ stub.assert_called_once_with("foo")
+ stub("foo")
+ with assert_traceback():
+ stub.assert_called_once_with("foo")
+
+
+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")
+ stub("foo")
+ stub.assert_called_once()
+ stub("foo")
+ with assert_traceback():
+ stub.assert_called_once()
+
+
+def test_assert_called_wrapper(mocker: MockerFixture) -> None:
+ stub = mocker.stub()
+ if not hasattr(stub, "assert_called"):
+ pytest.skip("assert_called_once not available")
+ with assert_traceback():
+ stub.assert_called()
+ stub("foo")
+ stub.assert_called()
+ stub("foo")
+ stub.assert_called()
+
+
+@pytest.mark.skip
+@pytest.mark.usefixtures("needs_assert_rewrite")
+def test_assert_called_args_with_introspection(mocker: MockerFixture) -> None:
+ stub = mocker.stub()
+
+ complex_args = ("a", 1, {"test"})
+ wrong_args = ("b", 2, {"jest"})
+
+ stub(*complex_args)
+ stub.assert_called_with(*complex_args)
+ stub.assert_called_once_with(*complex_args)
+
+ with assert_argument_introspection(complex_args, wrong_args):
+ stub.assert_called_with(*wrong_args)
+ stub.assert_called_once_with(*wrong_args)
+
+
+@pytest.mark.skip
+@pytest.mark.usefixtures("needs_assert_rewrite")
+def test_assert_called_kwargs_with_introspection(mocker: MockerFixture) -> None:
+ stub = mocker.stub()
+
+ complex_kwargs = dict(foo={"bar": 1, "baz": "spam"})
+ wrong_kwargs = dict(foo={"goo": 1, "baz": "bran"})
+
+ stub(**complex_kwargs)
+ stub.assert_called_with(**complex_kwargs)
+ stub.assert_called_once_with(**complex_kwargs)
+
+ with assert_argument_introspection(complex_kwargs, wrong_kwargs):
+ stub.assert_called_with(**wrong_kwargs)
+ stub.assert_called_once_with(**wrong_kwargs)
+
+
+def test_assert_any_call_wrapper(mocker: MockerFixture) -> None:
+ stub = mocker.stub()
+ stub("foo")
+ stub("foo")
+ stub.assert_any_call("foo")
+ with assert_traceback():
+ stub.assert_any_call("bar")
+
+
+def test_assert_has_calls(mocker: MockerFixture) -> None:
+ stub = mocker.stub()
+ stub("foo")
+ stub.assert_has_calls([mocker.call("foo")])
+ with assert_traceback():
+ stub.assert_has_calls([mocker.call("bar")])
+
+
+def test_assert_has_calls_multiple_calls(mocker: MockerFixture) -> None:
+ stub = mocker.stub()
+ stub("foo")
+ stub("bar")
+ stub("baz")
+ stub.assert_has_calls([mocker.call("foo"), mocker.call("bar"), mocker.call("baz")])
+ with assert_traceback():
+ stub.assert_has_calls(
+ [
+ mocker.call("foo"),
+ mocker.call("bar"),
+ mocker.call("baz"),
+ mocker.call("bat"),
+ ]
+ )
+ with assert_traceback():
+ stub.assert_has_calls(
+ [mocker.call("foo"), mocker.call("baz"), mocker.call("bar")]
+ )
+
+
+def test_assert_has_calls_multiple_calls_subset(mocker: MockerFixture) -> None:
+ stub = mocker.stub()
+ stub("foo")
+ stub("bar")
+ stub("baz")
+ stub.assert_has_calls([mocker.call("bar"), mocker.call("baz")])
+ with assert_traceback():
+ stub.assert_has_calls([mocker.call("foo"), mocker.call("baz")])
+ with assert_traceback():
+ stub.assert_has_calls(
+ [mocker.call("foo"), mocker.call("bar"), mocker.call("bat")]
+ )
+ with assert_traceback():
+ stub.assert_has_calls([mocker.call("baz"), mocker.call("bar")])
+
+
+def test_assert_has_calls_multiple_calls_any_order(mocker: MockerFixture) -> None:
+ stub = mocker.stub()
+ stub("foo")
+ stub("bar")
+ stub("baz")
+ stub.assert_has_calls(
+ [mocker.call("foo"), mocker.call("baz"), mocker.call("bar")], any_order=True
+ )
+ with assert_traceback():
+ stub.assert_has_calls(
+ [
+ mocker.call("foo"),
+ mocker.call("baz"),
+ mocker.call("bar"),
+ mocker.call("bat"),
+ ],
+ any_order=True,
+ )
+
+
+def test_assert_has_calls_multiple_calls_any_order_subset(
+ mocker: MockerFixture,
+) -> None:
+ stub = mocker.stub()
+ stub("foo")
+ stub("bar")
+ stub("baz")
+ stub.assert_has_calls([mocker.call("baz"), mocker.call("foo")], any_order=True)
+ with assert_traceback():
+ stub.assert_has_calls(
+ [mocker.call("baz"), mocker.call("foo"), mocker.call("bat")], any_order=True
+ )
+
+
+def test_assert_has_calls_no_calls(
+ mocker: MockerFixture,
+) -> None:
+ stub = mocker.stub()
+ stub.assert_has_calls([])
+ with assert_traceback():
+ stub.assert_has_calls([mocker.call("foo")])
+
+
+@pytest.mark.skip("Skip 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__
+
+ testdir.makepyfile(
+ """
+ def test_foo(mocker):
+ stub = mocker.stub()
+ assert stub.assert_called_with.__module__ == stub.__module__
+ """
+ )
+ testdir.makeini(
+ """
+ [pytest]
+ mock_traceback_monkeypatch = false
+ """
+ )
+ result = testdir.runpytest_subprocess()
+ assert result.ret == 0
+
+
+def test_parse_ini_boolean() -> None:
+ from pytest_mock._util import parse_ini_boolean
+
+ assert parse_ini_boolean("True") is True
+ assert parse_ini_boolean("false") is False
+ with pytest.raises(ValueError):
+ parse_ini_boolean("foo")
+
+
+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).
+ """
+
+ class Request:
+ @classmethod
+ def request(cls, method, args):
+ pass
+
+ m = mocker.patch.object(Request, "request")
+ Request.request(method="get", args={"type": "application/json"})
+ m.assert_called_once_with(method="get", args={"type": "application/json"})
+
+
+@pytest.mark.skip("Skip testdir")
+def test_monkeypatch_native(testdir: Any) -> None:
+ """Automatically disable monkeypatching when --tb=native."""
+ testdir.makepyfile(
+ """
+ def test_foo(mocker):
+ stub = mocker.stub()
+ stub(1, greet='hello')
+ stub.assert_called_once_with(1, greet='hey')
+ """
+ )
+ 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()
+ traceback_lines = [
+ x
+ for x in result.stdout.str().splitlines()
+ if "Traceback (most recent call last)" in x
+ ]
+ assert (
+ len(traceback_lines) == 1
+ ) # make sure there are no duplicated tracebacks (#44)
+
+
+@pytest.mark.skip("Skip testdir")
+def test_monkeypatch_no_terminal(testdir: Any) -> None:
+ """Don't crash without 'terminal' plugin."""
+ testdir.makepyfile(
+ """
+ def test_foo(mocker):
+ stub = mocker.stub()
+ stub(1, greet='hello')
+ stub.assert_called_once_with(1, greet='hey')
+ """
+ )
+ result = testdir.runpytest_subprocess("-p", "no:terminal", "-s")
+ assert result.ret == 1
+ assert result.stdout.lines == []
+
+
+@pytest.mark.skip("Skip testdir")
+def test_standalone_mock(testdir: Any) -> None:
+ """Check that the "mock_use_standalone" is being used."""
+ pytest.importorskip("mock")
+
+ testdir.makepyfile(
+ """
+ import mock
+
+ def test_foo(mocker):
+ assert mock.MagicMock is mocker.MagicMock
+ """
+ )
+ testdir.makeini(
+ """
+ [pytest]
+ mock_use_standalone_module = true
+ """
+ )
+ result = testdir.runpytest_subprocess()
+ assert result.ret == 0
+
+
+@pytest.mark.skip("Skip testdir")
+@pytest.mark.usefixtures("needs_assert_rewrite")
+def test_detailed_introspection(testdir: Any) -> None:
+ """Check that the "mock_use_standalone" is being used."""
+ testdir.makeini(
+ """
+ [pytest]
+ asyncio_mode=auto
+ """
+ )
+ testdir.makepyfile(
+ """
+ def test(mocker):
+ m = mocker.Mock()
+ m('fo')
+ m.assert_called_once_with('', bar=4)
+ """
+ )
+ result = testdir.runpytest("-s")
+ expected_lines = [
+ "*AssertionError: expected call not found.",
+ "*Expected: mock('', bar=4)",
+ "*Actual: mock('fo')",
+ ]
+ expected_lines += [
+ "*pytest introspection follows:*",
+ "*Args:",
+ "*assert ('fo',) == ('',)",
+ "*At index 0 diff: 'fo' != ''*",
+ "*Use -v to*",
+ "*Kwargs:*",
+ "*assert {} == {'bar': 4}*",
+ "*Right contains* more item*",
+ "*{'bar': 4}*",
+ "*Use -v to*",
+ ]
+ result.stdout.fnmatch_lines(expected_lines)
+
+
+@pytest.mark.skip("Skip testdir")
+@pytest.mark.usefixtures("needs_assert_rewrite")
+def test_detailed_introspection_async(testdir: Any) -> None:
+ """Check that the "mock_use_standalone" is being used."""
+ testdir.makeini(
+ """
+ [pytest]
+ asyncio_mode=auto
+ """
+ )
+ testdir.makepyfile(
+ """
+ import pytest
+
+ 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*",
+ "*Kwargs:*",
+ "*assert {} == {'bar': 4}*",
+ "*Right contains* more item*",
+ "*{'bar': 4}*",
+ "*Use -v to*",
+ ]
+ result.stdout.fnmatch_lines(expected_lines)
+
+
+@pytest.mark.skip("Skip testdir")
+def test_missing_introspection(testdir: Any) -> None:
+ testdir.makepyfile(
+ """
+ def test_foo(mocker):
+ mock = mocker.Mock()
+ mock('foo')
+ mock('test')
+ mock.assert_called_once_with('test')
+ """
+ )
+ result = testdir.runpytest()
+ assert "pytest introspection follows:" not in result.stdout.str()
+
+
+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"))
+
+ with pytest.raises(AssertionError):
+ stub.assert_called_with("lak")
+
+
+@pytest.mark.skip("Skip testdir")
+def test_plain_stopall(testdir: Any) -> None:
+ """patch.stopall() in a test should not cause an error during unconfigure (#137)"""
+ testdir.makeini(
+ """
+ [pytest]
+ asyncio_mode=auto
+ """
+ )
+ testdir.makepyfile(
+ """
+ import random
+
+ def get_random_number():
+ return random.randint(0, 100)
+
+ def test_get_random_number(mocker):
+ patcher = mocker.mock_module.patch("random.randint", lambda x, y: 5)
+ patcher.start()
+ assert get_random_number() == 5
+ mocker.mock_module.patch.stopall()
+ """
+ )
+ result = testdir.runpytest_subprocess()
+ result.stdout.fnmatch_lines("* 1 passed in *")
+ assert "RuntimeError" not in result.stderr.str()
+
+
+def test_warn_patch_object_context_manager(mocker: MockerFixture) -> None:
+ class A:
+ def doIt(self):
+ return False
+
+ a = A()
+
+ expected_warning_msg = (
+ "Mocks returned by pytest-mock do not need to be used as context managers. "
+ "The mocker fixture automatically undoes mocking at the end of a test. "
+ "This warning can be ignored if it was triggered by mocking a context manager. "
+ "https://pytest-mock.readthedocs.io/en/latest/remarks.html#usage-as-context-manager"
+ )
+
+ with pytest.warns(
+ PytestMockWarning, match=re.escape(expected_warning_msg)
+ ) as warn_record:
+ with mocker.patch.object(a, "doIt", return_value=True):
+ assert a.doIt() is True
+
+ assert warn_record[0].filename == __file__
+
+
+def test_warn_patch_context_manager(mocker: MockerFixture) -> None:
+ expected_warning_msg = (
+ "Mocks returned by pytest-mock do not need to be used as context managers. "
+ "The mocker fixture automatically undoes mocking at the end of a test. "
+ "This warning can be ignored if it was triggered by mocking a context manager. "
+ "https://pytest-mock.readthedocs.io/en/latest/remarks.html#usage-as-context-manager"
+ )
+
+ with pytest.warns(
+ PytestMockWarning, match=re.escape(expected_warning_msg)
+ ) as warn_record:
+ with mocker.patch("json.loads"):
+ pass
+
+ assert warn_record[0].filename == __file__
+
+
+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)"""
+
+ class dummy_module:
+ class MyContext:
+ def __enter__(self, *args, **kwargs):
+ return 10
+
+ def __exit__(self, *args, **kwargs):
+ pass
+
+ def my_func():
+ with dummy_module.MyContext() as v:
+ return v
+
+ mocker.patch.object(dummy_module, "MyContext")
+ assert isinstance(my_func(), mocker.MagicMock)
+
+
+def test_patch_context_manager_with_context_manager(mocker: MockerFixture) -> None:
+ """Test that no warnings are issued when an object patched with
+ patch.context_manager is used as a context manager (#221)"""
+
+ class A:
+ def doIt(self):
+ return False
+
+ a = A()
+
+ with warnings.catch_warnings(record=True) as warn_record:
+ with mocker.patch.context_manager(a, "doIt", return_value=True):
+ assert a.doIt() is True
+
+ assert len(warn_record) == 0
+
+
+@pytest.mark.skip("Skip 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
+
+ py_fn = testdir.makepyfile(
+ c="""
+ class C:
+ x = 1
+
+ def check(mocker):
+ mocker.patch.object(C, "x", 2)
+ assert C.x == 2
+ """
+ )
+ testdir.syspathinsert()
+
+ testdir.makepyfile(
+ """
+ from c import check
+ def test_foo(mocker):
+ check(mocker)
+ """
+ )
+ result = testdir.runpytest()
+ result.assert_outcomes(passed=1)
+
+ assert compileall.compile_file(str(py_fn), legacy=True)
+
+ pyc_fn = str(py_fn) + "c"
+ assert os.path.isfile(pyc_fn)
+
+ py_fn.remove()
+ result = testdir.runpytest()
+ result.assert_outcomes(passed=1)
+
+
+@pytest.mark.skip("Skip testdir")
+def test_used_with_class_scope(testdir: Any) -> None:
+ testdir.makeini(
+ """
+ [pytest]
+ asyncio_mode=auto
+ """
+ )
+ testdir.makepyfile(
+ """
+ import pytest
+ import random
+ import unittest
+
+ def get_random_number():
+ return random.randint(0, 1)
+
+ @pytest.fixture(autouse=True, scope="class")
+ def randint_mock(class_mocker):
+ return class_mocker.patch("random.randint", lambda x, y: 5)
+
+ class TestGetRandomNumber(unittest.TestCase):
+ def test_get_random_number(self):
+ assert get_random_number() == 5
+ """
+ )
+ result = testdir.runpytest_subprocess()
+ assert "AssertionError" not in result.stderr.str()
+ result.stdout.fnmatch_lines("* 1 passed in *")
+
+
+@pytest.mark.skip("Skip testdir")
+def test_used_with_module_scope(testdir: Any) -> None:
+ testdir.makeini(
+ """
+ [pytest]
+ asyncio_mode=auto
+ """
+ )
+ testdir.makepyfile(
+ """
+ import pytest
+ import random
+
+ def get_random_number():
+ return random.randint(0, 1)
+
+ @pytest.fixture(autouse=True, scope="module")
+ def randint_mock(module_mocker):
+ return module_mocker.patch("random.randint", lambda x, y: 5)
+
+ def test_get_random_number():
+ assert get_random_number() == 5
+ """
+ )
+ result = testdir.runpytest_subprocess()
+ assert "AssertionError" not in result.stderr.str()
+ result.stdout.fnmatch_lines("* 1 passed in *")
+
+
+@pytest.mark.skip("Skip testdir")
+def test_used_with_package_scope(testdir: Any) -> None:
+ testdir.makeini(
+ """
+ [pytest]
+ asyncio_mode=auto
+ """
+ )
+ testdir.makepyfile(
+ """
+ import pytest
+ import random
+
+ def get_random_number():
+ return random.randint(0, 1)
+
+ @pytest.fixture(autouse=True, scope="package")
+ def randint_mock(package_mocker):
+ return package_mocker.patch("random.randint", lambda x, y: 5)
+
+ def test_get_random_number():
+ assert get_random_number() == 5
+ """
+ )
+ result = testdir.runpytest_subprocess()
+ assert "AssertionError" not in result.stderr.str()
+ result.stdout.fnmatch_lines("* 1 passed in *")
+
+
+@pytest.mark.skip("Skip testdir")
+def test_used_with_session_scope(testdir: Any) -> None:
+ testdir.makeini(
+ """
+ [pytest]
+ asyncio_mode=auto
+ """
+ )
+ testdir.makepyfile(
+ """
+ import pytest
+ import random
+
+ def get_random_number():
+ return random.randint(0, 1)
+
+ @pytest.fixture(autouse=True, scope="session")
+ def randint_mock(session_mocker):
+ return session_mocker.patch("random.randint", lambda x, y: 5)
+
+ def test_get_random_number():
+ assert get_random_number() == 5
+ """
+ )
+ result = testdir.runpytest_subprocess()
+ assert "AssertionError" not in result.stderr.str()
+ result.stdout.fnmatch_lines("* 1 passed in *")
+
+
+def test_stop_patch(mocker):
+ class UnSpy:
+ def foo(self):
+ return 42
+
+ m = mocker.patch.object(UnSpy, "foo", return_value=0)
+ assert UnSpy().foo() == 0
+ mocker.stop(m)
+ assert UnSpy().foo() == 42
+
+ with pytest.raises(ValueError):
+ mocker.stop(m)
+
+
+def test_stop_instance_patch(mocker):
+ class UnSpy:
+ def foo(self):
+ return 42
+
+ m = mocker.patch.object(UnSpy, "foo", return_value=0)
+ un_spy = UnSpy()
+ assert un_spy.foo() == 0
+ mocker.stop(m)
+ assert un_spy.foo() == 42
+
+
+def test_stop_spy(mocker):
+ class UnSpy:
+ def foo(self):
+ return 42
+
+ spy = mocker.spy(UnSpy, "foo")
+ assert UnSpy().foo() == 42
+ assert spy.call_count == 1
+ mocker.stop(spy)
+ assert UnSpy().foo() == 42
+ assert spy.call_count == 1
+
+
+def test_stop_instance_spy(mocker):
+ class UnSpy:
+ def foo(self):
+ return 42
+
+ spy = mocker.spy(UnSpy, "foo")
+ un_spy = UnSpy()
+ assert un_spy.foo() == 42
+ assert spy.call_count == 1
+ mocker.stop(spy)
+ assert un_spy.foo() == 42
+ assert spy.call_count == 1
+
+
+def test_stop_multiple_patches(mocker: MockerFixture) -> None:
+ """Regression for #420."""
+
+ class Class1:
+ @staticmethod
+ def get():
+ return 1
+
+ class Class2:
+ @staticmethod
+ def get():
+ return 2
+
+ def handle_get():
+ return 3
+
+ mocker.patch.object(Class1, "get", handle_get)
+ mocker.patch.object(Class2, "get", handle_get)
+
+ mocker.stopall()
+
+ assert Class1.get() == 1
+ assert Class2.get() == 2
diff --git a/contrib/python/pytest-mock/py3/tests/ya.make b/contrib/python/pytest-mock/py3/tests/ya.make
new file mode 100644
index 0000000000..a8385e1263
--- /dev/null
+++ b/contrib/python/pytest-mock/py3/tests/ya.make
@@ -0,0 +1,16 @@
+PY3TEST()
+
+SUBSCRIBER(g:python-contrib)
+
+PEERDIR(
+ contrib/python/pytest-asyncio
+ contrib/python/pytest-mock
+)
+
+TEST_SRCS(
+ test_pytest_mock.py
+)
+
+NO_LINT()
+
+END()