You're right, ExceptionInfo in pytest is a powerful tool for inspecting exceptions raised during test execution. It provides a structured way to access details about the exception, such as its type, value, and traceback. This allows you to write more specific and informative tests that assert the correct exception is raised and potentially examine its properties.
Here's a code example demonstrating various ways to use ExceptionInfo with pytest.raises:
import pytest
def divide(x, y):
if y == 0:
raise ValueError("Cannot divide by zero")
return x / y
def test_division_by_zero_value_error(capsys):
with pytest.raises(ValueError) as excinfo:
divide(10, 0)
assert str(excinfo.value) == "Cannot divide by zero"
assert excinfo.type is ValueError
# You can also access the traceback (excinfo.traceback) but it's often
# more useful for debugging than direct assertion in tests.
print(f"Caught exception type: {excinfo.type}")
print(f"Caught exception value: {excinfo.value}")
captured = capsys.readouterr()
assert "Caught exception type: <class 'ValueError'>" in captured.out
assert "Caught exception value: Cannot divide by zero" in captured.out
def test_division_positive_numbers():
assert divide(10, 2) == 5
def test_division_non_integer_result():
assert divide(7, 2) == 3.5
def my_custom_exception(message):
class CustomError(Exception):
def __init__(self, msg):
super().__init__(msg)
self.error_message = msg
return CustomError(message)
def test_custom_exception_attributes():
with pytest.raises(Exception) as excinfo:
raise my_custom_exception("Something went wrong")
assert isinstance(excinfo.value, Exception)
assert excinfo.value.error_message == "Something went wrong"
def might_raise_different_exceptions(input_value):
if input_value < 0:
raise TypeError("Input cannot be negative")
elif input_value > 10:
raise OverflowError("Input too large")
else:
return input_value * 2
def test_multiple_possible_exceptions():
with pytest.raises((TypeError, OverflowError)) as excinfo:
might_raise_different_exceptions(-5)
assert excinfo.type is TypeError
assert str(excinfo.value) == "Input cannot be negative"
with pytest.raises((TypeError, OverflowError)) as excinfo:
might_raise_different_exceptions(15)
assert excinfo.type is OverflowError
assert str(excinfo.value) == "Input too large"
def function_that_warns():
import warnings
warnings.warn("This function might be slow", UserWarning)
return 42
def test_function_that_warns():
with pytest.warns(UserWarning) as record:
result = function_that_warns()
assert result == 42
assert len(record) == 1
assert str(record[0].message) == "This function might be slow"
assert record[0].category is UserWarningExplanation:
-
pytest.raises(ExpectedException) as excinfo:: This is the core construct.pytest.raises(ExpectedException): This context manager expects the code within thewithblock to raise theExpectedException(e.g.,ValueError,TypeError, a custom exception).as excinfo: If the expected exception is raised, anExceptionInfoobject is assigned to the variableexcinfo. If the exception is not raised or a different exception is raised, the test will fail.
-
Accessing Exception Details via
excinfo:excinfo.value: This attribute holds the actual exception instance that was raised. You can then access its attributes (as seen with theCustomErrorexample).excinfo.type: This attribute holds the type of the exception that was raised (e.g.,<class 'ValueError'>). You can compare it directly usingis.excinfo.traceback: This attribute provides access to the traceback information as aTracebackEntryobject. While you can inspect it, directly asserting on the traceback content in tests is generally discouraged as traceback details can be environment-dependent. It's more useful for debugging.
-
Asserting Exception Properties: You can use standard
assertstatements along with the information fromexcinfoto verify specific aspects of the raised exception, such as its error message or custom attributes. -
Expecting Multiple Exception Types:
pytest.raises((ExceptionType1, ExceptionType2))allows you to assert that one of the specified exception types is raised. -
Testing Warnings with
pytest.warns: While not strictly exceptions, pytest providespytest.warnswith a similarrecordobject to inspect warnings. Therecordobject is a list ofWarningInfoobjects, each containing information about a raised warning (message, category, etc.).
Key Benefits of Using ExceptionInfo:
- Precise Exception Testing: You can go beyond simply checking if an exception is raised and verify the specific type and even the details of the exception.
- Improved Test Clarity: Using
with pytest.raises(...) as excinfo:makes your intention clear – you are specifically testing for an expected exception. - Access to Exception Details: The
ExceptionInfoobject provides a structured way to access valuable information about the exception for more thorough assertions.
By leveraging ExceptionInfo, you can write more robust and informative tests that specifically target exception handling in your Python code.