Repository: pytest-dev/pytest-mock

Issue:
Tests fail with pytest-asyncio 0.17 due to asyncio-mode legacy warning
pytest-asyncio introduced a new mode parameter and warns if it is set to legacy:

```
[    5s] =================================== FAILURES ===================================
[    5s] ______________________________ test_plain_stopall ______________________________
[    5s] 
[    5s] testdir = <Testdir local('/tmp/pytest-of-abuild/pytest-77/test_plain_stopall0')>
[    5s] 
[    5s]     def test_plain_stopall(testdir: Any) -> None:
[    5s]         """patch.stopall() in a test should not cause an error during unconfigure (#137)"""
[    5s]         testdir.makepyfile(
[    5s]             """
[    5s]             import random
[    5s]     
[    5s]             def get_random_number():
[    5s]                 return random.randint(0, 100)
[    5s]     
[    5s]             def test_get_random_number(mocker):
[    5s]                 patcher = mocker.mock_module.patch("random.randint", lambda x, y: 5)
[    5s]                 patcher.start()
[    5s]                 assert get_random_number() == 5
[    5s]                 mocker.mock_module.patch.stopall()
[    5s]         """
[    5s]         )
[    5s]         result = testdir.runpytest_subprocess()
[    5s] >       result.stdout.fnmatch_lines("* 1 passed in *")
[    5s] E       Failed: nomatch: '* 1 passed in *'
[    5s] E           and: '============================= test session starts =============================='
[    5s] E           and: 'platform linux -- Python 3.9.9, pytest-6.2.5, py-1.10.0, pluggy-1.0.0'
[    5s] E           and: 'rootdir: /tmp/pytest-of-abuild/pytest-77/test_plain_stopall0'
[    5s] E           and: 'plugins: mock-3.6.1, asyncio-0.17.2'
[    5s] E           and: 'asyncio: mode=legacy'
[    5s] E           and: 'collected 1 item'
[    5s] E           and: ''
[    5s] E           and: 'test_plain_stopall.py .                                                  [100%]'
[    5s] E           and: ''
[    5s] E           and: '=============================== warnings summary ==============================='
[    5s] E           and: '../../../../usr/lib/python3.9/site-packages/pytest_asyncio/plugin.py:191'
[    5s] E           and: "  /usr/lib/python3.9/site-packages/pytest_asyncio/plugin.py:191: DeprecationWarning: The 'asyncio_mode' default value will change to 'strict' in future, please explicitly use 'asyncio_mode=strict' or 'asyncio_mode=auto' in pytest configuration file."
[    5s] E           and: '    config.issue_config_time_warning(LEGACY_MODE, stacklevel=2)'
[    5s] E           and: ''
[    5s] E           and: 'test_plain_stopall.py::test_get_random_number'
[    5s] E           and: "  /usr/lib/python3.9/site-packages/pytest_asyncio/plugin.py:317: DeprecationWarning: '@pytest.fixture' is applied to <fixture _mocker, file=/home/abuild/rpmbuild/BUILDROOT/python-pytest-mock-3.6.1-0.x86_64/usr/lib/python3.9/site-packages/pytest_mock/plugin.py, line=388> in 'legacy' mode, please replace it with '@pytest_asyncio.fixture' as a preparation for switching to 'strict' mode (or use 'auto' mode to seamlessly handle all these fixtures as asyncio-driven)."
[    5s] E           and: '    warnings.warn('
[    5s] E           and: ''
[    5s] E           and: '-- Docs: https://docs.pytest.org/en/stable/warnings.html'
[    5s] E           and: '======================== 1 passed, 2 warnings in 0.01s ========================='
[    5s] E       remains unmatched: '* 1 passed in *'
[    5s] 
[    5s] /home/abuild/rpmbuild/BUILD/pytest-mock-3.6.1/tests/test_pytest_mock.py:842: Failed
[    5s] ----------------------------- Captured stdout call -----------------------------
[    5s] running: /usr/bin/python3.9 -mpytest --basetemp=/tmp/pytest-of-abuild/pytest-77/test_plain_stopall0/runpytest-0
[    5s]      in: /tmp/pytest-of-abuild/pytest-77/test_plain_stopall0
[    5s] ============================= test session starts ==============================
[    5s] platform linux -- Python 3.9.9, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
[    5s] rootdir: /tmp/pytest-of-abuild/pytest-77/test_plain_stopall0
[    5s] plugins: mock-3.6.1, asyncio-0.17.2
[    5s] asyncio: mode=legacy
[    5s] collected 1 item
[    5s] 
[    5s] test_plain_stopall.py .                                                  [100%]
[    5s] 
[    5s] =============================== warnings summary ===============================
[    5s] ../../../../usr/lib/python3.9/site-packages/pytest_asyncio/plugin.py:191
[    5s]   /usr/lib/python3.9/site-packages/pytest_asyncio/plugin.py:191: DeprecationWarning: The 'asyncio_mode' default value will change to 'strict' in future, please explicitly use 'asyncio_mode=strict' or 'asyncio_mode=auto' in pytest configuration file.
[    5s]     config.issue_config_time_warning(LEGACY_MODE, stacklevel=2)
[    5s] 
[    5s] test_plain_stopall.py::test_get_random_number
[    5s]   /usr/lib/python3.9/site-packages/pytest_asyncio/plugin.py:317: DeprecationWarning: '@pytest.fixture' is applied to <fixture _mocker, file=/home/abuild/rpmbuild/BUILDROOT/python-pytest-mock-3.6.1-0.x86_64/usr/lib/python3.9/site-packages/pytest_mock/plugin.py, line=388> in 'legacy' mode, please replace it with '@pytest_asyncio.fixture' as a preparation for switching to 'strict' mode (or use 'auto' mode to seamlessly handle all these fixtures as asyncio-driven).
[    5s]     warnings.warn(
[    5s] 
[    5s] -- Docs: https://docs.pytest.org/en/stable/warnings.html
[    5s] ======================== 1 passed, 2 warnings in 0.01s =========================

...

[    5s] =========================== short test summary info ============================
[    5s] SKIPPED [4] tests/test_pytest_mock.py:38: this test needs assertion rewrite to work but current option is "plain"
[    5s] FAILED tests/test_pytest_mock.py::test_plain_stopall - Failed: nomatch: '* 1 ...
[    5s] FAILED tests/test_pytest_mock.py::test_used_with_class_scope - Failed: nomatc...
[    5s] FAILED tests/test_pytest_mock.py::test_used_with_module_scope - Failed: nomat...
[    5s] FAILED tests/test_pytest_mock.py::test_used_with_package_scope - Failed: noma...
[    5s] FAILED tests/test_pytest_mock.py::test_used_with_session_scope - Failed: noma...
[    5s] =================== 5 failed, 63 passed, 4 skipped in 2.07s ====================
```

These tests expect a testdir.pytest_subprocess run without warnings.

The easiest way is to patch the matching assertion
```diff
-    result.stdout.fnmatch_lines("* 1 passed in *")
+    result.stdout.fnmatch_lines("* 1 passed")
```
but I think it would be better to provide an explicit asyncio mode configuration to the testdir/pytester fixture.


Discussion:

Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index f8840c0..6969d9d 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -10,17 +10,17 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        python: ["3.6", "3.7", "3.8", "3.9"]
+        python: ["3.7", "3.8", "3.9", "3.10"]
         os: [ubuntu-latest, windows-latest]
         include:
-          - python: "3.6"
-            tox_env: "py36"
           - python: "3.7"
             tox_env: "py37"
           - python: "3.8"
             tox_env: "py38"
           - python: "3.9"
             tox_env: "py39"
+          - python: "3.10"
+            tox_env: "py310"
 
     steps:
     - uses: actions/checkout@v1
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 0f88309..6e1eafd 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,3 +1,9 @@
+3.7.0 (UNRELEASED)
+------------------
+
+* Python 3.10 now officially supported.
+* Dropped support for Python 3.6.
+
 3.6.1 (2021-05-06)
 ------------------
 
diff --git a/HOWTORELEASE.rst b/RELEASING.rst
similarity index 100%
rename from HOWTORELEASE.rst
rename to RELEASING.rst
diff --git a/mypy.ini b/mypy.ini
index 2b6bae7..2daaec5 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -2,6 +2,7 @@
 disallow_any_generics = True
 disallow_incomplete_defs = True
 disallow_subclassing_any = True
+ignore_missing_imports = True
 no_implicit_optional = True
 pretty = True
 show_error_codes = True
diff --git a/setup.py b/setup.py
index 96db74f..7de0ab4 100644
--- a/setup.py
+++ b/setup.py
@@ -12,7 +12,7 @@ setup(
     package_data={
         "pytest_mock": ["py.typed"],
     },
-    python_requires=">=3.6",
+    python_requires=">=3.7",
     install_requires=["pytest>=5.0"],
     use_scm_version={"write_to": "src/pytest_mock/_version.py"},
     setup_requires=["setuptools_scm"],
@@ -31,10 +31,10 @@ setup(
         "License :: OSI Approved :: MIT License",
         "Operating System :: OS Independent",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
         "Programming Language :: Python :: 3 :: Only",
         "Topic :: Software Development :: Testing",
     ],
diff --git a/tox.ini b/tox.ini
index 5a2030a..2af93bf 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
 minversion = 3.5.3
-envlist = py{35,36,37,38,39}, linting, norewrite
+envlist = py{37,38,39,310}, linting, norewrite
 
 [testenv]
 passenv = USER USERNAME
@@ -28,6 +28,7 @@ commands = mypy {posargs:src tests}
 
 [pytest]
 addopts = -r a
+asyncio_mode = auto
 
 [flake8]
 max-line-length = 88


Test patch:
diff --git a/tests/test_pytest_mock.py b/tests/test_pytest_mock.py
index 6d8feda..cca50ad 100644
--- a/tests/test_pytest_mock.py
+++ b/tests/test_pytest_mock.py
@@ -211,7 +211,7 @@ class TestMockerStub:
     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)
+        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"
@@ -267,19 +267,19 @@ def test_instance_method_spy_exception(
 ) -> None:
     class Foo:
         def bar(self, arg):
-            raise exc_cls("Error with {}".format(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="Error with {}".format(v)):
+        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) == "Error with {}".format(v)
+        assert str(spy.spy_exception) == f"Error with {v}"
 
 
 def test_instance_method_spy_autospec_true(mocker: MockerFixture) -> None:
@@ -296,7 +296,7 @@ def test_instance_method_spy_autospec_true(mocker: MockerFixture) -> None:
 
 
 def test_spy_reset(mocker: MockerFixture) -> None:
-    class Foo(object):
+    class Foo:
         def bar(self, x):
             if x == 0:
                 raise ValueError("invalid x")
@@ -475,7 +475,6 @@ def test_callable_like_spy(testdir: Any, mocker: MockerFixture) -> None:
     assert spy.spy_return == 20
 
 
-@pytest.mark.asyncio
 async def test_instance_async_method_spy(mocker: MockerFixture) -> None:
     class Foo:
         async def bar(self, arg):
@@ -728,6 +727,12 @@ def test_standalone_mock(testdir: Any) -> None:
 @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):
@@ -769,11 +774,16 @@ def test_detailed_introspection(testdir: Any) -> None:
 @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
 
-        @pytest.mark.asyncio
         async def test(mocker):
             m = mocker.AsyncMock()
             await m('fo')
@@ -824,6 +834,12 @@ def test_assert_called_with_unicode_arguments(mocker: MockerFixture) -> None:
 
 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
@@ -958,6 +974,12 @@ def test_abort_patch_context_manager_with_stale_pyc(testdir: Any) -> None:
 
 
 def test_used_with_class_scope(testdir: Any) -> None:
+    testdir.makeini(
+        """
+        [pytest]
+        asyncio_mode=auto
+        """
+    )
     testdir.makepyfile(
         """
         import pytest
@@ -982,6 +1004,12 @@ def test_used_with_class_scope(testdir: Any) -> None:
 
 
 def test_used_with_module_scope(testdir: Any) -> None:
+    testdir.makeini(
+        """
+        [pytest]
+        asyncio_mode=auto
+        """
+    )
     testdir.makepyfile(
         """
         import pytest
@@ -1004,7 +1032,12 @@ def test_used_with_module_scope(testdir: Any) -> None:
 
 
 def test_used_with_package_scope(testdir: Any) -> None:
-    """..."""
+    testdir.makeini(
+        """
+        [pytest]
+        asyncio_mode=auto
+        """
+    )
     testdir.makepyfile(
         """
         import pytest
@@ -1027,7 +1060,12 @@ def test_used_with_package_scope(testdir: Any) -> None:
 
 
 def test_used_with_session_scope(testdir: Any) -> None:
-    """..."""
+    testdir.makeini(
+        """
+        [pytest]
+        asyncio_mode=auto
+        """
+    )
     testdir.makepyfile(
         """
         import pytest