Skip to content

Commit 2264db7

Browse files
authored
Merge pull request #4682 from arel/parameterize-conditional-raises-document-only
Document parametrizing conditional raises
2 parents 7dbe400 + 7ec1a14 commit 2264db7

File tree

5 files changed

+105
-0
lines changed

5 files changed

+105
-0
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Anthony Shaw
2727
Anthony Sottile
2828
Anton Lodder
2929
Antony Lee
30+
Arel Cordero
3031
Armin Rigo
3132
Aron Coyle
3233
Aron Curzon

changelog/4324.doc.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Document how to use ``raises`` and ``does_not_raise`` to write parametrized tests with conditional raises.

doc/en/example/parametrize.rst

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,3 +565,50 @@ As the result:
565565
- The test ``test_eval[1+7-8]`` passed, but the name is autogenerated and confusing.
566566
- The test ``test_eval[basic_2+4]`` passed.
567567
- The test ``test_eval[basic_6*9]`` was expected to fail and did fail.
568+
569+
.. _`parametrizing_conditional_raising`:
570+
571+
Parametrizing conditional raising
572+
--------------------------------------------------------------------
573+
574+
Use :func:`pytest.raises` with the
575+
:ref:`pytest.mark.parametrize ref` decorator to write parametrized tests
576+
in which some tests raise exceptions and others do not.
577+
578+
It is helpful to define a no-op context manager ``does_not_raise`` to serve
579+
as a complement to ``raises``. For example::
580+
581+
from contextlib import contextmanager
582+
import pytest
583+
584+
@contextmanager
585+
def does_not_raise():
586+
yield
587+
588+
589+
@pytest.mark.parametrize('example_input,expectation', [
590+
(3, does_not_raise()),
591+
(2, does_not_raise()),
592+
(1, does_not_raise()),
593+
(0, pytest.raises(ZeroDivisionError)),
594+
])
595+
def test_division(example_input, expectation):
596+
"""Test how much I know division."""
597+
with expectation:
598+
assert (6 / example_input) is not None
599+
600+
In the example above, the first three test cases should run unexceptionally,
601+
while the fourth should raise ``ZeroDivisionError``.
602+
603+
If you're only supporting Python 3.7+, you can simply use ``nullcontext``
604+
to define ``does_not_raise``::
605+
606+
from contextlib import nullcontext as does_not_raise
607+
608+
Or, if you're supporting Python 3.3+ you can use::
609+
610+
from contextlib import ExitStack as does_not_raise
611+
612+
Or, if desired, you can ``pip install contextlib2`` and use::
613+
614+
from contextlib2 import ExitStack as does_not_raise

src/_pytest/python_api.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,14 @@ def raises(expected_exception, *args, **kwargs):
621621
...
622622
>>> assert exc_info.type is ValueError
623623
624+
**Using with** ``pytest.mark.parametrize``
625+
626+
When using :ref:`pytest.mark.parametrize ref`
627+
it is possible to parametrize tests such that
628+
some runs raise an exception and others do not.
629+
630+
See :ref:`parametrizing_conditional_raising` for an example.
631+
624632
**Legacy form**
625633
626634
It is possible to specify a callable by passing a to-be-called lambda::

testing/python/raises.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,54 @@ def test_raise_wrong_exception_passes_by():
9494
result = testdir.runpytest()
9595
result.stdout.fnmatch_lines(["*3 passed*"])
9696

97+
def test_does_not_raise(self, testdir):
98+
testdir.makepyfile(
99+
"""
100+
from contextlib import contextmanager
101+
import pytest
102+
103+
@contextmanager
104+
def does_not_raise():
105+
yield
106+
107+
@pytest.mark.parametrize('example_input,expectation', [
108+
(3, does_not_raise()),
109+
(2, does_not_raise()),
110+
(1, does_not_raise()),
111+
(0, pytest.raises(ZeroDivisionError)),
112+
])
113+
def test_division(example_input, expectation):
114+
'''Test how much I know division.'''
115+
with expectation:
116+
assert (6 / example_input) is not None
117+
"""
118+
)
119+
result = testdir.runpytest()
120+
result.stdout.fnmatch_lines(["*4 passed*"])
121+
122+
def test_does_not_raise_does_raise(self, testdir):
123+
testdir.makepyfile(
124+
"""
125+
from contextlib import contextmanager
126+
import pytest
127+
128+
@contextmanager
129+
def does_not_raise():
130+
yield
131+
132+
@pytest.mark.parametrize('example_input,expectation', [
133+
(0, does_not_raise()),
134+
(1, pytest.raises(ZeroDivisionError)),
135+
])
136+
def test_division(example_input, expectation):
137+
'''Test how much I know division.'''
138+
with expectation:
139+
assert (6 / example_input) is not None
140+
"""
141+
)
142+
result = testdir.runpytest()
143+
result.stdout.fnmatch_lines(["*2 failed*"])
144+
97145
def test_noclass(self):
98146
with pytest.raises(TypeError):
99147
pytest.raises("wrong", lambda: None)

0 commit comments

Comments
 (0)