In the previous chapter, we learned that any raised exception that is not handled within a pytest test case function will cause that test case to report a failure. So, this begs an important question: How do we verify that a piece of code successfully raises an exception?
Thankfully, pytest provides a mechanism for safely catching and verifying expected exceptions. Let’s learn how it works.
For example, let's verify that dividing by zero raises an exception. In our test_math.py
module, add a new test case function named test_divide_by_zero
and implement it as follows:
def test_divide_by_zero():
num = 1 / 0
This single line alone should raise an exception. Let's see what happens when we run our new test case.
As expected, the test fails. Dividing by zero raises a ZeroDivisionError
exception with the message, "division by zero". The math operation correctly raised the exception, but letting it rise outside of the test case function caused the test to fail. Thankfully, pytest safely catches any and all unhandled exceptions, performs any cleanup, and moves on to the next test case. Exceptions for one test case won't affect other tests.
So, how can we properly handle and verify exceptions inside of a test case? We could write our own try/except block, but thankfully, pytest actually provides a construct for handling expected exceptions: pytest.raises
.
To use it, first import pytest into the test module.
import pytest
Then add the following lines to our existing test case:
def test_divide_by_zero():
with pytest.raises(ZeroDivisionError) as e:
num = 1 / 0
assert 'division by zero' in str(e.value)
In Python, with
is a special statement for automatically handling extra "enter" and "exit" logic for a caller. It is most commonly used for file input and output. However, f or pytest.raises
, the "enter" logic makes the code catch any exceptions, and the "exit" logic asserts if the desired exception type was actually raised.
In our case, one divided by zero should raise a ZeroDivisionError
. So pytest.raises
should catch the exception and keep running the test as if there were no problems.
Furthermore, pytest.raises
looks for an exception of a specific type. If the steps within the statement's body do not raise the desired exception, then it will raise an assertion error to fail the test. Using pytest.raises
makes the test code more concise and avoids repetitive try/except blocks.
In addition to verifying that the code raises an exception, we can also verify attributes of the raised exception. Notice how the with
statement stores the exception object as a variable named e
. We can verify the exception's message with the following line:
assert 'division by zero' in str(e.value)
Let's run our updated tests.
Boom! This time it runs flawlessly. The test catches the expected exception and verifies its message.
Exception handling is a core part of any programming language. When writing unit tests, we should always make sure functions and methods raise proper exceptions under expected conditions. pytest’s mechanism for verifying exceptions eliminates the need for explicit try/except blocks in your tests.
with
statement