Skip to content

Commit 5beab07

Browse files
committed
improve warning message. Make it warn regardless of autouse or not. Add section to deprecations.rst
1 parent 283db4e commit 5beab07

File tree

6 files changed

+93
-26
lines changed

6 files changed

+93
-26
lines changed

changelog/10839.deprecation.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
Synchronous tests that request an asynchronous fixture with ``autouse=True`` will now give a DeprecationWarning.
1+
Synchronous tests that request an asynchronous fixture will now give a DeprecationWarning. This will introduce warnings in several pytest plugins that handle async tests/fixtures and for some users with custom setups. For guidance on how to manage this see :ref:`sync-test-async-fixture`.

changelog/10839.improvement.rst

Lines changed: 0 additions & 1 deletion
This file was deleted.

doc/en/deprecations.rst

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,61 @@ Below is a complete list of all pytest features which are considered deprecated.
1515
:class:`~pytest.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`.
1616

1717

18+
.. _sync-test-async-fixture:
19+
20+
sync test depending on async fixture
21+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
22+
23+
.. deprecated:: 8.4
24+
25+
Pytest has for a long time given an error when encountering an asynchronous test function, prompting the user to install
26+
a plugin that can handle it. It has not given any errors if you have an asynchronous fixture that's depended on by a
27+
synchronous test. This is a problem even if you do have a plugin installed for handling async tests, as they may require
28+
special decorators for async fixtures to be handled, and some may not robustly handle if a user accidentally requests an
29+
async fixture from their sync tests. Fixture values being cached can make this even more unintuitive, where everything will
30+
"work" if the fixture is first requested by an async test, and then requested by a synchronous test.
31+
32+
Unfortunately there is no 100% reliable method of identifying when a user has made a mistake, versus when they expect an
33+
unawaited object from their fixture that they will handle - either on their own, or by a plugin. To suppress this warning
34+
when you in fact did intend to handle this you can wrap your async fixture in a synchronous fixture:
35+
36+
.. code-block:: python
37+
38+
import asyncio
39+
import pytest
40+
41+
42+
@pytest.fixture
43+
async def unawaited_fixture():
44+
return 1
45+
46+
47+
def test_foo(unawaited_fixture):
48+
assert 1 == asyncio.run(unawaited_fixture)
49+
50+
should be changed to
51+
52+
53+
.. code-block:: python
54+
55+
import asyncio
56+
import pytest
57+
58+
59+
@pytest.fixture
60+
def unawaited_fixture():
61+
async def inner_fixture():
62+
return 1
63+
64+
return inner_fixture()
65+
66+
67+
def test_foo(unawaited_fixture):
68+
assert 1 == asyncio.run(unawaited_fixture)
69+
70+
If a user has an async fixture with ``autouse=True`` in their ``conftest.py``, or in a file where they also have synchronous tests, they will also get this warning. We strongly recommend against this practice, and they should restructure their testing infrastructure so the fixture is synchronous or to separate the fixture from their synchronous tests. Plugins that handle async may want to introduce helpers to make that easier in scenarios where that might be wanted, e.g. if setting up a database in an asynchronous way, or the user may opt to make their test async even though it might not strictly need to be.
71+
72+
1873
.. _import-or-skip-import-error:
1974

2075
``pytest.importorskip`` default behavior regarding :class:`ImportError`

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ disable = [
308308
]
309309

310310
[tool.codespell]
311-
ignore-words-list = "afile,asser,assertio,feld,hove,ned,noes,notin,paramete,parth,socio-economic,tesults,varius,wil"
311+
ignore-words-list = "afile,asend,asser,assertio,feld,hove,ned,noes,notin,paramete,parth,socio-economic,tesults,varius,wil"
312312
skip = "*/plugin_list.rst"
313313
write-changes = true
314314

src/_pytest/fixtures.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -609,23 +609,26 @@ def _get_active_fixturedef(
609609
if fixturedef._autouse:
610610
warnings.warn(
611611
PytestRemovedIn9Warning(
612-
"Sync test requested an async fixture with autouse=True. "
612+
f"Sync test {self._pyfuncitem.name!r} requested async fixture "
613+
f"{argname!r} with autouse=True. "
613614
"If you intended to use the fixture you may want to make the "
614-
"test asynchronous. If you did not intend to use it you should "
615+
"test asynchronous or the fixture synchronous. "
616+
"If you did not intend to use it you should "
615617
"restructure your test setup. "
616618
"This will turn into an error in pytest 9."
617619
),
618620
stacklevel=3,
619621
)
620622
else:
621-
raise FixtureLookupError(
622-
argname,
623-
self,
624-
(
625-
"ERROR: Sync test requested async fixture. "
623+
warnings.warn(
624+
PytestRemovedIn9Warning(
625+
f"Sync test {self._pyfuncitem.name!r} requested async fixture "
626+
f"{argname!r}. "
626627
"You may want to make the test asynchronous and run it with "
627-
"a suitable async framework test plugin."
628+
"a suitable async framework test plugin, or make the fixture synchronous. "
629+
"This will turn into an error in pytest 9."
628630
),
631+
stacklevel=3,
629632
)
630633

631634
# Prepare a SubRequest object for calling the fixture.

testing/acceptance_test.py

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,21 +1296,26 @@ async def async_fixture():
12961296
...
12971297
12981298
def test_foo(async_fixture):
1299-
...
1299+
# suppress unawaited coroutine warning
1300+
try:
1301+
async_fixture.send(None)
1302+
except StopIteration:
1303+
pass
13001304
"""
13011305
)
13021306
result = pytester.runpytest()
13031307
result.stdout.fnmatch_lines(
13041308
[
13051309
(
1306-
"*ERROR: Sync test requested async fixture. "
1310+
"*Sync test 'test_foo' requested async fixture "
1311+
"'async_fixture'. "
13071312
"You may want to make the test asynchronous and run it with "
1308-
"a suitable async framework test plugin.*"
1313+
"a suitable async framework test plugin, or make the fixture synchronous. "
1314+
"This will turn into an error in pytest 9."
13091315
),
1310-
"*ERROR test_sync.py::test_foo*",
13111316
]
13121317
)
1313-
result.assert_outcomes(errors=1)
1318+
result.assert_outcomes(passed=1, warnings=1)
13141319

13151320

13161321
def test_error_on_sync_test_async_fixture_gen(pytester: Pytester) -> None:
@@ -1323,21 +1328,26 @@ async def async_fixture():
13231328
yield
13241329
13251330
def test_foo(async_fixture):
1326-
...
1331+
# suppress unawaited coroutine warning
1332+
try:
1333+
async_fixture.asend(None)
1334+
except StopIteration:
1335+
pass
13271336
"""
13281337
)
13291338
result = pytester.runpytest()
13301339
result.stdout.fnmatch_lines(
13311340
[
13321341
(
1333-
"*ERROR: Sync test requested async fixture. "
1342+
"*Sync test 'test_foo' requested async fixture "
1343+
"'async_fixture'. "
13341344
"You may want to make the test asynchronous and run it with "
1335-
"a suitable async framework test plugin.*"
1345+
"a suitable async framework test plugin, or make the fixture synchronous. "
1346+
"This will turn into an error in pytest 9."
13361347
),
1337-
"*ERROR test_sync.py::test_foo*",
13381348
]
13391349
)
1340-
result.assert_outcomes(errors=1)
1350+
result.assert_outcomes(passed=1, warnings=1)
13411351

13421352

13431353
def test_warning_on_sync_test_async_autouse_fixture(pytester: Pytester) -> None:
@@ -1362,11 +1372,11 @@ def test_foo(async_fixture):
13621372
result.stdout.fnmatch_lines(
13631373
[
13641374
(
1365-
"*Sync test requested an async fixture with autouse=True. "
1366-
"If you intended to use the fixture you may want to make the "
1367-
"test asynchronous. If you did not intend to use it you should "
1368-
"restructure your test setup. "
1369-
"This will turn into an error in pytest 9.*"
1375+
"*Sync test 'test_foo' requested async fixture "
1376+
"'async_fixture' with autouse=True. "
1377+
"You may want to make the test asynchronous and run it with "
1378+
"a suitable async framework test plugin, or make the fixture synchronous. "
1379+
"This will turn into an error in pytest 9."
13701380
),
13711381
]
13721382
)

0 commit comments

Comments
 (0)