Skip to content

Instantly share code, notes, and snippets.

@howardrotterdam
Created May 4, 2025 09:22
Show Gist options
  • Select an option

  • Save howardrotterdam/050330ebb1e2bceb4122306f9140100f to your computer and use it in GitHub Desktop.

Select an option

Save howardrotterdam/050330ebb1e2bceb4122306f9140100f to your computer and use it in GitHub Desktop.
Show code example for usage of ExceptionInfo in pytest

Gemini 2.0flash:

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 UserWarning

Explanation:

  1. pytest.raises(ExpectedException) as excinfo:: This is the core construct.

    • pytest.raises(ExpectedException): This context manager expects the code within the with block to raise the ExpectedException (e.g., ValueError, TypeError, a custom exception).
    • as excinfo: If the expected exception is raised, an ExceptionInfo object is assigned to the variable excinfo. If the exception is not raised or a different exception is raised, the test will fail.
  2. 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 the CustomError example).
    • excinfo.type: This attribute holds the type of the exception that was raised (e.g., <class 'ValueError'>). You can compare it directly using is.
    • excinfo.traceback: This attribute provides access to the traceback information as a TracebackEntry object. 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.
  3. Asserting Exception Properties: You can use standard assert statements along with the information from excinfo to verify specific aspects of the raised exception, such as its error message or custom attributes.

  4. Expecting Multiple Exception Types: pytest.raises((ExceptionType1, ExceptionType2)) allows you to assert that one of the specified exception types is raised.

  5. Testing Warnings with pytest.warns: While not strictly exceptions, pytest provides pytest.warns with a similar record object to inspect warnings. The record object is a list of WarningInfo objects, 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 ExceptionInfo object 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment