Skip to content

Commit 3c8dff2

Browse files
committed
Reintroduce PytestReturnNotNoneWarning
Since this warning is meant to be permanent, added proper documentation to the `assert` section in the docs. Fixes #13477
1 parent 216c7ec commit 3c8dff2

File tree

8 files changed

+74
-48
lines changed

8 files changed

+74
-48
lines changed

changelog/13477.bugfix.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Reintroduced :class:`pytest.PytestReturnNotNoneWarning` which was removed by accident in pytest `8.4`.
2+
3+
This warning is raised when a test functions returns a value other than ``None``, which is often a mistake made by beginners.
4+
5+
See :ref:`return-not-none` for more information.

doc/en/deprecations.rst

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -316,46 +316,6 @@ Users expected in this case that the ``usefixtures`` mark would have its intende
316316
Now pytest will issue a warning when it encounters this problem, and will raise an error in the future versions.
317317

318318

319-
Returning non-None value in test functions
320-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
321-
322-
.. deprecated:: 7.2
323-
324-
A ``pytest.PytestReturnNotNoneWarning`` is now emitted if a test function returns something other than `None`.
325-
326-
This prevents a common mistake among beginners that expect that returning a `bool` would cause a test to pass or fail, for example:
327-
328-
.. code-block:: python
329-
330-
@pytest.mark.parametrize(
331-
["a", "b", "result"],
332-
[
333-
[1, 2, 5],
334-
[2, 3, 8],
335-
[5, 3, 18],
336-
],
337-
)
338-
def test_foo(a, b, result):
339-
return foo(a, b) == result
340-
341-
Given that pytest ignores the return value, this might be surprising that it will never fail.
342-
343-
The proper fix is to change the `return` to an `assert`:
344-
345-
.. code-block:: python
346-
347-
@pytest.mark.parametrize(
348-
["a", "b", "result"],
349-
[
350-
[1, 2, 5],
351-
[2, 3, 8],
352-
[5, 3, 18],
353-
],
354-
)
355-
def test_foo(a, b, result):
356-
assert foo(a, b) == result
357-
358-
359319
The ``yield_fixture`` function/decorator
360320
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
361321

doc/en/how-to/assert.rst

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,50 @@ the conftest file:
476476
FAILED test_foocompare.py::test_compare - assert Comparing Foo instances:
477477
1 failed in 0.12s
478478
479+
.. _`return-not-none`:
480+
481+
Returning non-None value in test functions
482+
------------------------------------------
483+
484+
A :class:`pytest.PytestReturnNotNoneWarning` is emitted when a test function returns a value other than ``None``.
485+
486+
This helps prevent a common mistake made by beginners who assume that returning a ``bool`` (e.g., ``True`` or ``False``) will determine whether a test passes or fails.
487+
488+
Example:
489+
490+
.. code-block:: python
491+
492+
@pytest.mark.parametrize(
493+
["a", "b", "result"],
494+
[
495+
[1, 2, 5],
496+
[2, 3, 8],
497+
[5, 3, 18],
498+
],
499+
)
500+
def test_foo(a, b, result):
501+
return foo(a, b) == result # Incorrect usage, do not do this.
502+
503+
Since pytest ignores return values, it might be surprising that the test will never fail based on the returned value.
504+
505+
The correct fix is to replace the ``return`` statement with an ``assert``:
506+
507+
.. code-block:: python
508+
509+
@pytest.mark.parametrize(
510+
["a", "b", "result"],
511+
[
512+
[1, 2, 5],
513+
[2, 3, 8],
514+
[5, 3, 18],
515+
],
516+
)
517+
def test_foo(a, b, result):
518+
assert foo(a, b) == result
519+
520+
521+
522+
479523
.. _assert-details:
480524
.. _`assert introspection`:
481525

doc/en/reference/reference.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,6 +1274,9 @@ Custom warnings generated in some situations such as improper usage or deprecate
12741274
.. autoclass:: pytest.PytestExperimentalApiWarning
12751275
:show-inheritance:
12761276

1277+
.. autoclass:: pytest.PytestReturnNotNoneWarning
1278+
:show-inheritance:
1279+
12771280
.. autoclass:: pytest.PytestRemovedIn9Warning
12781281
:show-inheritance:
12791282

src/_pytest/python.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
from _pytest.scope import Scope
7474
from _pytest.stash import StashKey
7575
from _pytest.warning_types import PytestCollectionWarning
76+
from _pytest.warning_types import PytestReturnNotNoneWarning
7677

7778

7879
if TYPE_CHECKING:
@@ -157,12 +158,12 @@ def pytest_pyfunc_call(pyfuncitem: Function) -> object | None:
157158
if hasattr(result, "__await__") or hasattr(result, "__aiter__"):
158159
async_fail(pyfuncitem.nodeid)
159160
elif result is not None:
160-
fail(
161-
(
162-
f"Expected None, but test returned {result!r}. "
163-
"Did you mean to use `assert` instead of `return`?"
164-
),
165-
pytrace=False,
161+
warnings.warn(
162+
PytestReturnNotNoneWarning(
163+
f"Test functions should return None, but {pyfuncitem.nodeid} returned {type(result)!r}.\n"
164+
"Did you mean to use `assert` instead of `return`?\n"
165+
"See https://docs.pytest.org/en/stable/how-to/assert.html#return-not-none for more information."
166+
)
166167
)
167168
return True
168169

src/_pytest/warning_types.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,17 @@ def simple(cls, apiname: str) -> PytestExperimentalApiWarning:
7171
return cls(f"{apiname} is an experimental api that may change over time")
7272

7373

74+
@final
75+
class PytestReturnNotNoneWarning(PytestWarning):
76+
"""
77+
Warning emitted when a test function is returning value other than None.
78+
79+
See :ref:`return-not-none` for details.
80+
"""
81+
82+
__module__ = "pytest"
83+
84+
7485
@final
7586
class PytestUnknownMarkWarning(PytestWarning):
7687
"""Warning emitted on use of unknown markers.

src/pytest/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
from _pytest.warning_types import PytestExperimentalApiWarning
8383
from _pytest.warning_types import PytestFDWarning
8484
from _pytest.warning_types import PytestRemovedIn9Warning
85+
from _pytest.warning_types import PytestReturnNotNoneWarning
8586
from _pytest.warning_types import PytestUnhandledThreadExceptionWarning
8687
from _pytest.warning_types import PytestUnknownMarkWarning
8788
from _pytest.warning_types import PytestUnraisableExceptionWarning
@@ -132,6 +133,7 @@
132133
"PytestFDWarning",
133134
"PytestPluginManager",
134135
"PytestRemovedIn9Warning",
136+
"PytestReturnNotNoneWarning",
135137
"PytestUnhandledThreadExceptionWarning",
136138
"PytestUnknownMarkWarning",
137139
"PytestUnraisableExceptionWarning",

testing/acceptance_test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,15 +1489,15 @@ def test_no_brokenpipeerror_message(pytester: Pytester) -> None:
14891489
popen.stderr.close()
14901490

14911491

1492-
def test_function_return_non_none_error(pytester: Pytester) -> None:
1492+
@pytest.mark.filterwarnings("default")
1493+
def test_function_return_non_none_warning(pytester: Pytester) -> None:
14931494
pytester.makepyfile(
14941495
"""
14951496
def test_stuff():
14961497
return "something"
14971498
"""
14981499
)
14991500
res = pytester.runpytest()
1500-
res.assert_outcomes(failed=1)
15011501
res.stdout.fnmatch_lines(["*Did you mean to use `assert` instead of `return`?*"])
15021502

15031503

0 commit comments

Comments
 (0)