Skip to content

Commit 6728ec5

Browse files
committed
Raise error if sync test relies on async fixture, and warn if the fixture is autouse.
1 parent 256203a commit 6728ec5

File tree

2 files changed

+122
-1
lines changed

2 files changed

+122
-1
lines changed

src/_pytest/fixtures.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
from _pytest.scope import _ScopeName
7474
from _pytest.scope import HIGH_SCOPES
7575
from _pytest.scope import Scope
76+
from _pytest.warning_types import PytestRemovedIn9Warning
7677

7778

7879
if sys.version_info < (3, 11):
@@ -575,6 +576,7 @@ def _get_active_fixturedef(
575576
# The are no fixtures with this name applicable for the function.
576577
if not fixturedefs:
577578
raise FixtureLookupError(argname, self)
579+
578580
# A fixture may override another fixture with the same name, e.g. a
579581
# fixture in a module can override a fixture in a conftest, a fixture in
580582
# a class can override a fixture in the module, and so on.
@@ -593,6 +595,32 @@ def _get_active_fixturedef(
593595
raise FixtureLookupError(argname, self)
594596
fixturedef = fixturedefs[index]
595597

598+
if not inspect.iscoroutinefunction(self.function) and (
599+
inspect.iscoroutinefunction(fixturedef.func)
600+
or inspect.isasyncgenfunction(fixturedef.func)
601+
):
602+
if fixturedef._autouse:
603+
warnings.warn(
604+
PytestRemovedIn9Warning(
605+
"Sync test requested an async fixture with autouse=True. "
606+
"If you intended to use the fixture you may want to make the "
607+
"test asynchronous. If you did not intend to use it you should "
608+
"restructure your test setup. "
609+
"This will turn into an error in pytest 9."
610+
),
611+
stacklevel=3,
612+
)
613+
else:
614+
raise FixtureLookupError(
615+
argname,
616+
self,
617+
(
618+
"ERROR: Sync test requested async fixture. "
619+
"You may want to make the test asynchronous and run it with "
620+
"a suitable async framework test plugin."
621+
),
622+
)
623+
596624
# Prepare a SubRequest object for calling the fixture.
597625
try:
598626
callspec = self._pyfuncitem.callspec
@@ -805,7 +833,7 @@ def formatrepr(self) -> FixtureLookupErrorRepr:
805833
stack = [self.request._pyfuncitem.obj]
806834
stack.extend(map(lambda x: x.func, self.fixturestack))
807835
msg = self.msg
808-
if msg is not None:
836+
if msg is not None and len(stack) > 1:
809837
# The last fixture raise an error, let's present
810838
# it at the requesting side.
811839
stack = stack[:-1]
@@ -959,6 +987,8 @@ def __init__(
959987
ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None,
960988
*,
961989
_ispytest: bool = False,
990+
# only used to emit a deprecationwarning, can be removed in pytest9
991+
_autouse: bool = False,
962992
) -> None:
963993
check_ispytest(_ispytest)
964994
# The "base" node ID for the fixture.
@@ -1005,6 +1035,9 @@ def __init__(
10051035
self.cached_result: _FixtureCachedResult[FixtureValue] | None = None
10061036
self._finalizers: Final[list[Callable[[], object]]] = []
10071037

1038+
# only used to emit a deprecationwarning, can be removed in pytest9
1039+
self._autouse = _autouse
1040+
10081041
@property
10091042
def scope(self) -> _ScopeName:
10101043
"""Scope string, one of "function", "class", "module", "package", "session"."""
@@ -1666,6 +1699,7 @@ def _register_fixture(
16661699
params=params,
16671700
ids=ids,
16681701
_ispytest=True,
1702+
_autouse=autouse,
16691703
)
16701704

16711705
faclist = self._arg2fixturedefs.setdefault(name, [])

testing/acceptance_test.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,6 +1286,93 @@ def test_3():
12861286
result.assert_outcomes(failed=3)
12871287

12881288

1289+
def test_error_on_sync_test_async_fixture(pytester: Pytester) -> None:
1290+
pytester.makepyfile(
1291+
test_sync="""
1292+
import pytest
1293+
1294+
@pytest.fixture
1295+
async def async_fixture():
1296+
...
1297+
1298+
def test_foo(async_fixture):
1299+
...
1300+
"""
1301+
)
1302+
result = pytester.runpytest()
1303+
result.stdout.fnmatch_lines(
1304+
[
1305+
(
1306+
"*ERROR: Sync test requested async fixture. "
1307+
"You may want to make the test asynchronous and run it with "
1308+
"a suitable async framework test plugin.*"
1309+
),
1310+
"*ERROR test_sync.py::test_foo*",
1311+
]
1312+
)
1313+
result.assert_outcomes(errors=1)
1314+
1315+
1316+
def test_error_on_sync_test_async_fixture_gen(pytester: Pytester) -> None:
1317+
pytester.makepyfile(
1318+
test_sync="""
1319+
import pytest
1320+
1321+
@pytest.fixture
1322+
async def async_fixture():
1323+
yield
1324+
1325+
def test_foo(async_fixture):
1326+
...
1327+
"""
1328+
)
1329+
result = pytester.runpytest()
1330+
result.stdout.fnmatch_lines(
1331+
[
1332+
(
1333+
"*ERROR: Sync test requested async fixture. "
1334+
"You may want to make the test asynchronous and run it with "
1335+
"a suitable async framework test plugin.*"
1336+
),
1337+
"*ERROR test_sync.py::test_foo*",
1338+
]
1339+
)
1340+
result.assert_outcomes(errors=1)
1341+
1342+
1343+
def test_warning_on_sync_test_async_autouse_fixture(pytester: Pytester) -> None:
1344+
pytester.makepyfile(
1345+
test_sync="""
1346+
import pytest
1347+
1348+
@pytest.fixture(autouse=True)
1349+
async def async_fixture():
1350+
...
1351+
1352+
# We explicitly request the fixture to be able to
1353+
# suppress the RuntimeWarning for unawaited coroutine.
1354+
def test_foo(async_fixture):
1355+
try:
1356+
async_fixture.send(None)
1357+
except StopIteration:
1358+
pass
1359+
"""
1360+
)
1361+
result = pytester.runpytest()
1362+
result.stdout.fnmatch_lines(
1363+
[
1364+
(
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.*"
1370+
),
1371+
]
1372+
)
1373+
result.assert_outcomes(passed=1)
1374+
1375+
12891376
def test_pdb_can_be_rewritten(pytester: Pytester) -> None:
12901377
pytester.makepyfile(
12911378
**{

0 commit comments

Comments
 (0)