Skip to content

Reintroduce PytestReturnNotNoneWarning #13495

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog/13477.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Reintroduced :class:`pytest.PytestReturnNotNoneWarning` which was removed by accident in pytest `8.4`.

This warning is raised when a test functions returns a value other than ``None``, which is often a mistake made by beginners.

See :ref:`return-not-none` for more information.
40 changes: 0 additions & 40 deletions doc/en/deprecations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -316,46 +316,6 @@ Users expected in this case that the ``usefixtures`` mark would have its intende
Now pytest will issue a warning when it encounters this problem, and will raise an error in the future versions.


Returning non-None value in test functions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. deprecated:: 7.2

A ``pytest.PytestReturnNotNoneWarning`` is now emitted if a test function returns something other than `None`.

This prevents a common mistake among beginners that expect that returning a `bool` would cause a test to pass or fail, for example:

.. code-block:: python

@pytest.mark.parametrize(
["a", "b", "result"],
[
[1, 2, 5],
[2, 3, 8],
[5, 3, 18],
],
)
def test_foo(a, b, result):
return foo(a, b) == result

Given that pytest ignores the return value, this might be surprising that it will never fail.

The proper fix is to change the `return` to an `assert`:

.. code-block:: python

@pytest.mark.parametrize(
["a", "b", "result"],
[
[1, 2, 5],
[2, 3, 8],
[5, 3, 18],
],
)
def test_foo(a, b, result):
assert foo(a, b) == result


The ``yield_fixture`` function/decorator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand Down
44 changes: 44 additions & 0 deletions doc/en/how-to/assert.rst
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,50 @@ the conftest file:
FAILED test_foocompare.py::test_compare - assert Comparing Foo instances:
1 failed in 0.12s

.. _`return-not-none`:

Returning non-None value in test functions
------------------------------------------

A :class:`pytest.PytestReturnNotNoneWarning` is emitted when a test function returns a value other than ``None``.

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.

Example:

.. code-block:: python

@pytest.mark.parametrize(
["a", "b", "result"],
[
[1, 2, 5],
[2, 3, 8],
[5, 3, 18],
],
)
def test_foo(a, b, result):
return foo(a, b) == result # Incorrect usage, do not do this.

Since pytest ignores return values, it might be surprising that the test will never fail based on the returned value.

The correct fix is to replace the ``return`` statement with an ``assert``:

.. code-block:: python

@pytest.mark.parametrize(
["a", "b", "result"],
[
[1, 2, 5],
[2, 3, 8],
[5, 3, 18],
],
)
def test_foo(a, b, result):
assert foo(a, b) == result




.. _assert-details:
.. _`assert introspection`:

Expand Down
3 changes: 3 additions & 0 deletions doc/en/reference/reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,9 @@ Custom warnings generated in some situations such as improper usage or deprecate
.. autoclass:: pytest.PytestExperimentalApiWarning
:show-inheritance:

.. autoclass:: pytest.PytestReturnNotNoneWarning
:show-inheritance:

.. autoclass:: pytest.PytestRemovedIn9Warning
:show-inheritance:

Expand Down
13 changes: 7 additions & 6 deletions src/_pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
from _pytest.scope import Scope
from _pytest.stash import StashKey
from _pytest.warning_types import PytestCollectionWarning
from _pytest.warning_types import PytestReturnNotNoneWarning


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

Expand Down
11 changes: 11 additions & 0 deletions src/_pytest/warning_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ def simple(cls, apiname: str) -> PytestExperimentalApiWarning:
return cls(f"{apiname} is an experimental api that may change over time")


@final
class PytestReturnNotNoneWarning(PytestWarning):
"""
Warning emitted when a test function returns a value other than ``None``.

See :ref:`return-not-none` for details.
"""

__module__ = "pytest"


@final
class PytestUnknownMarkWarning(PytestWarning):
"""Warning emitted on use of unknown markers.
Expand Down
2 changes: 2 additions & 0 deletions src/pytest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
from _pytest.warning_types import PytestExperimentalApiWarning
from _pytest.warning_types import PytestFDWarning
from _pytest.warning_types import PytestRemovedIn9Warning
from _pytest.warning_types import PytestReturnNotNoneWarning
from _pytest.warning_types import PytestUnhandledThreadExceptionWarning
from _pytest.warning_types import PytestUnknownMarkWarning
from _pytest.warning_types import PytestUnraisableExceptionWarning
Expand Down Expand Up @@ -132,6 +133,7 @@
"PytestFDWarning",
"PytestPluginManager",
"PytestRemovedIn9Warning",
"PytestReturnNotNoneWarning",
"PytestUnhandledThreadExceptionWarning",
"PytestUnknownMarkWarning",
"PytestUnraisableExceptionWarning",
Expand Down
4 changes: 2 additions & 2 deletions testing/acceptance_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1489,15 +1489,15 @@ def test_no_brokenpipeerror_message(pytester: Pytester) -> None:
popen.stderr.close()


def test_function_return_non_none_error(pytester: Pytester) -> None:
@pytest.mark.filterwarnings("default")
def test_function_return_non_none_warning(pytester: Pytester) -> None:
pytester.makepyfile(
"""
def test_stuff():
return "something"
"""
)
res = pytester.runpytest()
res.assert_outcomes(failed=1)
res.stdout.fnmatch_lines(["*Did you mean to use `assert` instead of `return`?*"])


Expand Down
Loading