Skip to content

Update documentation on how pytest.warns affects DeprecationWarning #9291 #10141

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

Merged
merged 2 commits into from
Aug 17, 2022
Merged
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
1 change: 1 addition & 0 deletions changelog/9291.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Update documentation on how :func:`pytest.warns` affects :class:`DeprecationWarning`.
42 changes: 31 additions & 11 deletions doc/en/how-to/capture-warnings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ Running pytest now produces this output:
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
======================= 1 passed, 1 warning in 0.12s =======================

.. _`controlling-warnings`:

Controlling warnings
--------------------

Expand Down Expand Up @@ -176,11 +178,14 @@ using an external system.
DeprecationWarning and PendingDeprecationWarning
------------------------------------------------


By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning`` warnings from
user code and third-party libraries, as recommended by :pep:`565`.
This helps users keep their code modern and avoid breakages when deprecated warnings are effectively removed.

However, in the specific case where users capture any type of warnings in their test, either with
:func:`pytest.warns`, :func:`pytest.deprecated_call` or using the :ref:`recwarn <recwarn>` fixture,
no warning will be displayed at all.

Sometimes it is useful to hide some specific deprecation warnings that happen in code that you have no control over
(such as third-party libraries), in which case you might use the warning filters options (ini or marks) to ignore
those warnings.
Expand All @@ -197,6 +202,9 @@ For example:
This will ignore all warnings of type ``DeprecationWarning`` where the start of the message matches
the regular expression ``".*U.*mode is deprecated"``.

See :ref:`@pytest.mark.filterwarnings <filterwarnings>` and
:ref:`Controlling warnings <controlling-warnings>` for more examples.

.. note::

If warnings are configured at the interpreter level, using
Expand Down Expand Up @@ -245,10 +253,10 @@ when called with a ``17`` argument.
Asserting warnings with the warns function
------------------------------------------



You can check that code raises a particular warning using :func:`pytest.warns`,
which works in a similar manner to :ref:`raises <assertraises>`:
which works in a similar manner to :ref:`raises <assertraises>` (except that
:ref:`raises <assertraises>` does not capture all exceptions, only the
``expected_exception``):

.. code-block:: python

Expand All @@ -261,8 +269,8 @@ which works in a similar manner to :ref:`raises <assertraises>`:
with pytest.warns(UserWarning):
warnings.warn("my warning", UserWarning)

The test will fail if the warning in question is not raised. The keyword
argument ``match`` to assert that the exception matches a text or regex::
The test will fail if the warning in question is not raised. Use the keyword
argument ``match`` to assert that the warning matches a text or regex::

>>> with warns(UserWarning, match='must be 0 or None'):
... warnings.warn("value must be 0 or None", UserWarning)
Expand Down Expand Up @@ -359,20 +367,32 @@ Additional use cases of warnings in tests

Here are some use cases involving warnings that often come up in tests, and suggestions on how to deal with them:

- To ensure that **at least one** warning is emitted, use:
- To ensure that **at least one** of the indicated warnings is issued, use:

.. code-block:: python

with pytest.warns():
def test_warning():
with pytest.warns((RuntimeWarning, UserWarning)):
...

- To ensure that **only** certain warnings are issued, use:

.. code-block:: python

def test_warning(recwarn):
...
assert len(recwarn) == 1
user_warning = recwarn.pop(UserWarning)
assert issubclass(user_warning.category, UserWarning)

- To ensure that **no** warnings are emitted, use:

.. code-block:: python

with warnings.catch_warnings():
warnings.simplefilter("error")
...
def test_warning():
with warnings.catch_warnings():
warnings.simplefilter("error")
...

- To suppress warnings, use:

Expand Down
22 changes: 15 additions & 7 deletions src/_pytest/recwarn.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,15 @@ def warns(
) -> Union["WarningsChecker", Any]:
r"""Assert that code raises a particular class of warning.

Specifically, the parameter ``expected_warning`` can be a warning class or
sequence of warning classes, and the code inside the ``with`` block must issue a warning of that class or
classes.
Specifically, the parameter ``expected_warning`` can be a warning class or sequence
of warning classes, and the code inside the ``with`` block must issue at least one
warning of that class or classes.

This helper produces a list of :class:`warnings.WarningMessage` objects,
one for each warning raised.
This helper produces a list of :class:`warnings.WarningMessage` objects, one for
each warning raised (regardless of whether it is an ``expected_warning`` or not).

This function can be used as a context manager, or any of the other ways
:func:`pytest.raises` can be used::
This function can be used as a context manager, which will capture all the raised
warnings inside it::

>>> import pytest
>>> with pytest.warns(RuntimeWarning):
Expand All @@ -139,6 +139,14 @@ def warns(
...
Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...

**Using with** ``pytest.mark.parametrize``

When using :ref:`pytest.mark.parametrize ref` it is possible to parametrize tests
such that some runs raise a warning and others do not.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please add an example here to illustrate this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we put a link to Parametrizing conditional raising and point out that it is done in a similar way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example:

This could be achieved in the same way as with exceptions, see 
:ref:`parametrizing_conditional_raising` for an example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicoddemus we have added the link. Please let us know if this is acceptable or if a specific example is needed.


This could be achieved in the same way as with exceptions, see
:ref:`parametrizing_conditional_raising` for an example.

"""
__tracebackhide__ = True
if not args:
Expand Down