Testing exceptions with pytest.raises

pytest · Essentials · 8 min

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 cause NameError.
  • For exception groups (Python 3.11+), use pytest.RaisesGroup in 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.

Check yourself

  1. What happens if the code inside `with pytest.raises(KeyError):` raises nothing?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.pytest.org/en/stable/how-to/assert.html#assertions-about-expected-exceptions