Error paths are behavior too, and untested validation code is where bugs hide.
To test that code fails correctly, use pytest.raises as a context manager:
with pytest.raises(ValueError): followed by the call. The test passes only if that exception type (or a subclass) is raised inside the block. If nothing is raised, the test fails with "DID NOT RAISE".
Check the message with match=, a regular expression searched in str(exception): pytest.raises(ValueError, match="must be positive"). Escape special characters with re.escape when matching literal text containing ., ( or [.
Bind the result with as excinfo to inspect the exception after the block: excinfo.value is the exception object (check custom attributes like an error code), and excinfo.type its class.
Rules of thumb:
- Put only the call that should raise inside the block. Any earlier line that raises the same type would make the test pass for the wrong reason.
- Be specific:
pytest.raises(Exception)passes for almost any bug, including typos that causeNameError. - For exception groups (Python 3.11+), use
pytest.RaisesGroupin recent pytest versions.
For warnings there's an analogous pytest.warns, covered later.
Example
# test_account.py
import pytest
class InsufficientFunds(Exception):
def __init__(self, missing):
super().__init__(f"missing {missing:.2f}")
self.missing = missing
def withdraw(balance, amount):
if amount <= 0:
raise ValueError("amount must be positive")
if amount > balance:
raise InsufficientFunds(amount - balance)
return balance - amount
def test_rejects_negative():
with pytest.raises(ValueError, match="must be positive"):
withdraw(100, -5)
def test_insufficient_funds_reports_missing():
with pytest.raises(InsufficientFunds) as excinfo:
withdraw(10, 25)
assert excinfo.value.missing == 15
assert str(excinfo.value) == "missing 15.00"Remember
- with pytest.raises(Type): wraps the call that must raise
- match= is a regex searched in the exception message
- excinfo.value is the exception object for further asserts
- Keep only the raising call inside the block and be specific about the type
Watch out for
- Using pytest.raises(Exception), which also passes on unrelated bugs like a NameError.