Pytest Raises Regex: Validate Exception Messages
Testing for raised exceptions is a fundamental part of writing
resilient Python code, and verifying the exact error text ensures that
failures occur for the intended reasons. This article explains how the
pytest.raises context manager uses regular expressions via
its match parameter to capture and validate exception
messages, how to escape special characters, and how to apply regex
patterns to test dynamic error strings effectively.
The match Parameter
The pytest.raises context manager accepts a
match parameter that tests the string representation of an
exception against a regular expression. Under the hood, pytest evaluates
the pattern using Python's re.search() function, meaning
the regex only needs to match a portion of the exception message rather
than the entire string.
import pytest
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero.")
return a / b
def test_divide_by_zero():
with pytest.raises(ValueError, match=r"divide by zero"):
divide(10, 0)If the function raises the expected ValueError and the
message contains "divide by zero", the test passes. If the
message does not match, pytest fails the test and outputs both the
pattern and the actual exception message.
Matching Dynamic and Formatted Messages
When an exception message includes dynamic data, such as IDs, variable names, or timestamps, you can leverage standard regular expression syntax to validate the output format without hardcoding changing values.
def process_user(user_id):
if user_id < 0:
raise ValueError(f"Invalid user ID: {user_id}. Must be positive.")
def test_process_user_invalid():
# Matches "Invalid user ID: <digits>. Must be positive."
with pytest.raises(ValueError, match=r"Invalid user ID: -\d+\. Must be positive\."):
process_user(-42)Useful regex constructs for exception testing include:
^and$: Anchor the match to the start and end of the string to enforce an exact full-string match.\d+: Match one or more numeric digits..*: Match any sequence of characters between known substrings.(?i): Enable case-insensitive matching at the start of the pattern.
Escaping Special Regex Characters
Because match interprets strings as regular expressions,
literal characters that carry syntactic meaning in regex—such as
parentheses, brackets, question marks, and periods—must be escaped.
Failing to escape these characters can lead to regex compilation errors or false test failures:
import re
def parse_input(data):
raise ValueError("Invalid input [code: 400] (fatal)")
def test_parse_input_manual_escape():
# Escaping brackets and parentheses manually
with pytest.raises(ValueError, match=r"Invalid input \[code: 400\] \(fatal\)"):
parse_input("bad_data")
def test_parse_input_auto_escape():
# Using re.escape for exact literal matching
expected_message = "Invalid input [code: 400] (fatal)"
with pytest.raises(ValueError, match=re.escape(expected_message)):
parse_input("bad_data")Using re.escape() is the safest approach when you want
to assert the entire string as a literal without regular expression
evaluation.
Inspecting Exceptions
via ExceptionInfo
While the match parameter is the preferred, idiomatic
way to test error strings, pytest also provides access to the underlying
exception object using the as clause. This allows for
manual regex checks using the standard re module if complex
multi-step assertions are required.
import re
def test_manual_regex_inspection():
with pytest.raises(ValueError) as exc_info:
raise ValueError("Error code: ERR_1042 occurred.")
exception_message = str(exc_info.value)
assert re.search(r"ERR_\d+", exception_message) is not NoneAccessing exc_info.value converts the error directly to
its string representation, allowing standard assertions on custom
exception attributes in addition to regex matching.