Bird
Raised Fist0
PyTesttesting~15 mins

Checking exception attributes in PyTest - Deep Dive

Choose your learning style10 modes available
Overview - Checking exception attributes
What is it?
Checking exception attributes means verifying specific details inside an error that a program raises during testing. When a test expects an error, it is important not only to catch that error but also to confirm that its message or other properties are correct. This helps ensure the program fails in the right way and for the right reasons. Pytest provides tools to capture exceptions and inspect their attributes easily.
Why it matters
Without checking exception attributes, tests might pass even if the error is not the one expected or if the error message is wrong. This can hide bugs or cause confusion when debugging. By verifying exception details, developers get clear feedback about failures, making software more reliable and easier to fix. It prevents silent errors and improves trust in automated tests.
Where it fits
Before learning this, you should understand basic pytest test functions and how to write simple tests that expect exceptions. After this, you can learn advanced pytest features like custom exception handling, fixtures, and parameterized tests to build robust test suites.
Mental Model
Core Idea
Checking exception attributes means catching an error during a test and then looking inside it to confirm it has the exact message or properties expected.
Think of it like...
It's like catching a letter in the mail and then opening it to read the exact message inside, not just knowing that a letter arrived.
┌─────────────────────────────┐
│ Run test code               │
│ ┌─────────────────────────┐ │
│ │ Exception raised?       │─┐│
│ └─────────────────────────┘ │
│           Yes               │
│           │                 │
│ ┌─────────▼─────────────┐   │
│ │ Capture exception info│   │
│ └─────────┬─────────────┘   │
│           │                 │
│ ┌─────────▼─────────────┐   │
│ │ Check exception attrs │   │
│ └─────────┬─────────────┘   │
│           │                 │
│ ┌─────────▼─────────────┐   │
│ │ Pass or Fail test     │   │
│ └──────────────────────┘   │
└─────────────────────────────┘
Build-Up - 6 Steps
1
FoundationBasic exception catching in pytest
🤔
Concept: Learn how to catch exceptions in pytest using the 'pytest.raises' context manager.
In pytest, you can check if a block of code raises an exception by using 'with pytest.raises(ExpectedException):'. This will pass the test if the exception occurs, and fail if it does not. Example: import pytest def test_zero_division(): with pytest.raises(ZeroDivisionError): 1 / 0
Result
The test passes because dividing by zero raises ZeroDivisionError as expected.
Understanding how to catch exceptions is the first step to verifying error handling in your code.
2
FoundationAccessing the exception object
🤔
Concept: Learn how to capture the exception object to inspect its details.
Pytest allows you to capture the exception object by assigning the 'pytest.raises' context manager to a variable using 'as excinfo'. This variable holds information about the exception. Example: import pytest def test_zero_division_message(): with pytest.raises(ZeroDivisionError) as excinfo: 1 / 0 assert 'division by zero' in str(excinfo.value)
Result
The test passes because the exception message contains 'division by zero'.
Capturing the exception object lets you check not just that an error happened, but also what the error says.
3
IntermediateChecking specific exception attributes
🤔Before reading on: do you think you can check custom attributes on exceptions the same way as the message? Commit to your answer.
Concept: Learn how to check attributes other than the message on exceptions, such as custom properties.
Some exceptions have extra information stored in attributes. You can access these attributes from 'excinfo.value' just like any object. Example with a custom exception: class MyError(Exception): def __init__(self, code): self.code = code super().__init__('Error with code ' + str(code)) def test_custom_exception(): with pytest.raises(MyError) as excinfo: raise MyError(404) assert excinfo.value.code == 404
Result
The test passes because the exception's 'code' attribute equals 404.
Knowing you can check any attribute on the exception object allows precise validation of error details.
4
IntermediateUsing regex to check exception messages
🤔Before reading on: do you think checking exception messages with regex is more flexible than simple substring checks? Commit to your answer.
Concept: Learn to use regular expressions to match complex patterns in exception messages.
Pytest's 'raises' supports a 'match' parameter that takes a regex pattern to check the exception message. Example: import pytest def test_value_error_match(): with pytest.raises(ValueError, match=r'number \d+ is invalid'): raise ValueError('number 42 is invalid')
Result
The test passes because the exception message matches the regex pattern.
Using regex for message matching makes tests robust against small message changes and allows pattern-based checks.
5
AdvancedInspecting chained exceptions attributes
🤔Before reading on: do you think pytest's excinfo captures chained exceptions automatically? Commit to your answer.
Concept: Learn how to access attributes of exceptions that are linked by chaining (using 'from' keyword).
Python exceptions can be chained to show cause. Pytest's excinfo captures the outer exception, but you can access the inner one via '__cause__'. Example: import pytest def test_chained_exception(): with pytest.raises(RuntimeError) as excinfo: try: raise ValueError('inner error') except ValueError as e: raise RuntimeError('outer error') from e assert excinfo.value.__cause__.args[0] == 'inner error'
Result
The test passes because the inner exception message is correctly accessed.
Understanding chained exceptions helps diagnose complex error flows and write precise tests.
6
ExpertCustom exception attribute validation hooks
🤔Before reading on: do you think pytest allows automatic validation of exception attributes beyond manual asserts? Commit to your answer.
Concept: Learn how to create reusable pytest fixtures or hooks to validate exception attributes automatically.
In large projects, you can write helper functions or fixtures that check exception attributes to avoid repeating code. Example fixture: import pytest def check_myerror(excinfo, expected_code): assert isinstance(excinfo.value, MyError) assert excinfo.value.code == expected_code @pytest.fixture def myerror_checker(): return check_myerror # Usage in test: def test_with_fixture(myerror_checker): with pytest.raises(MyError) as excinfo: raise MyError(500) myerror_checker(excinfo, 500)
Result
The test passes and attribute checks are reusable and clean.
Automating attribute checks improves test maintainability and reduces human error in large test suites.
Under the Hood
Pytest's 'raises' is a context manager that runs the code inside its block and catches exceptions of the expected type. It stores the caught exception object in an internal structure accessible via 'excinfo'. This object holds the exception instance, traceback, and other metadata. When you access 'excinfo.value', you get the actual exception instance, allowing inspection of its attributes. The 'match' parameter uses Python's 're' module to match the exception message string against a regex pattern.
Why designed this way?
Pytest was designed to make testing exceptions simple and expressive. Using a context manager fits Python's style and allows clean syntax. Capturing the exception object enables detailed checks beyond just the type. The 'match' parameter was added to avoid verbose manual string checks. This design balances ease of use with flexibility, avoiding complex decorators or separate assertion functions.
┌─────────────────────────────┐
│ pytest.raises context manager│
│ ┌─────────────────────────┐ │
│ │ Run test code block     │ │
│ └─────────────┬───────────┘ │
│               │ Exception?   │
│               ▼             │
│ ┌─────────────────────────┐ │
│ │ Catch exception object   │ │
│ └─────────────┬───────────┘ │
│               │ Store in excinfo│
│               ▼             │
│ ┌─────────────────────────┐ │
│ │ excinfo.value = exception│ │
│ │ excinfo.type = exception │ │
│ │ excinfo.traceback = tb   │ │
│ └─────────────────────────┘ │
└─────────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does pytest.raises catch exceptions other than the specified type? Commit to yes or no.
Common Belief:Pytest.raises will catch any exception raised inside its block, regardless of type.
Tap to reveal reality
Reality:Pytest.raises only catches exceptions of the specified type or its subclasses. Other exceptions will cause the test to error out.
Why it matters:If you expect a specific error but a different one occurs, pytest will not catch it silently. This helps avoid false positives but can confuse beginners who expect all exceptions to be caught.
Quick: Can you check exception attributes without capturing excinfo? Commit to yes or no.
Common Belief:You can check exception attributes directly in the 'pytest.raises' call without capturing excinfo.
Tap to reveal reality
Reality:You must capture excinfo using 'as' to access exception attributes. Without it, you only know the exception type was raised.
Why it matters:Trying to check attributes without excinfo leads to incomplete tests that miss verifying important error details.
Quick: Does the 'match' parameter in pytest.raises use simple substring matching? Commit to yes or no.
Common Belief:The 'match' parameter checks if the exception message contains the given string as a substring.
Tap to reveal reality
Reality:The 'match' parameter uses regular expressions, not simple substring matching.
Why it matters:Misunderstanding this can cause tests to fail unexpectedly or pass when they shouldn't, especially with special regex characters.
Quick: Does excinfo.value always contain the original exception in chained exceptions? Commit to yes or no.
Common Belief:excinfo.value always holds the first exception raised, even if exceptions are chained.
Tap to reveal reality
Reality:excinfo.value holds the outermost exception. The original (inner) exception is accessible via the '__cause__' attribute.
Why it matters:Not knowing this can cause tests to check the wrong exception details and miss the root cause.
Expert Zone
1
Some exceptions override the __str__ method to generate messages dynamically, so checking attributes directly can be more reliable than matching messages.
2
When testing async code, pytest.raises works with 'async with' syntax, but capturing exception attributes requires careful handling of coroutines.
3
Custom exception classes may store sensitive data in attributes; tests should avoid exposing this in logs or assertions to maintain security.
When NOT to use
Checking exception attributes is not suitable when the exception message or attributes are unstable or change frequently. In such cases, focus on exception type only or use mocks to simulate error conditions.
Production Patterns
In production test suites, teams often create helper functions or pytest fixtures to standardize exception attribute checks. This reduces duplication and enforces consistent error handling policies across many tests.
Connections
Error handling in programming
Builds-on
Understanding how exceptions carry information helps grasp broader error handling patterns in software development.
Regular expressions
Same pattern
Using regex to check exception messages connects testing to pattern matching, a fundamental concept in text processing.
Forensic science
Analogy in investigation
Just like forensic experts examine clues to understand a crime, testers inspect exception attributes to understand why a program failed.
Common Pitfalls
#1Not capturing the exception object to check attributes.
Wrong approach:def test_error(): with pytest.raises(ValueError): raise ValueError('bad value') assert 'bad value' in str(excinfo.value)
Correct approach:def test_error(): with pytest.raises(ValueError) as excinfo: raise ValueError('bad value') assert 'bad value' in str(excinfo.value)
Root cause:Trying to access 'excinfo' without assigning the context manager to a variable means the exception object is not captured.
#2Using 'match' parameter with a plain string expecting substring match.
Wrong approach:with pytest.raises(ValueError, match='error?'): raise ValueError('error!')
Correct approach:with pytest.raises(ValueError, match='error!'): raise ValueError('error!')
Root cause:Misunderstanding that 'match' uses regex causes unexpected test failures due to special characters.
#3Assuming excinfo.value holds the original exception in chained exceptions.
Wrong approach:with pytest.raises(RuntimeError) as excinfo: try: raise ValueError('inner') except ValueError: raise RuntimeError('outer') assert excinfo.value.args[0] == 'inner'
Correct approach:with pytest.raises(RuntimeError) as excinfo: try: raise ValueError('inner') except ValueError as e: raise RuntimeError('outer') from e assert excinfo.value.__cause__.args[0] == 'inner'
Root cause:Not using the '__cause__' attribute to access the inner exception leads to checking the wrong error message.
Key Takeaways
Checking exception attributes in pytest means capturing the exception object to verify its message or custom properties.
Using 'pytest.raises' with 'as excinfo' allows detailed inspection of exceptions beyond just their type.
The 'match' parameter uses regular expressions, enabling flexible and powerful message checks.
Understanding chained exceptions and accessing inner exceptions via '__cause__' helps diagnose complex error flows.
Creating reusable helpers for exception attribute checks improves test clarity and maintainability in large projects.

Practice

(1/5)
1. What is the correct way to capture an exception and check its message using pytest.raises?
easy
A. Use pytest.raises(ValueError) as exc_info: outside a with block.
B. Use pytest.raises(ValueError) without as exc_info and check exc_info.message.
C. Use with pytest.raises() as exc_info: and check exc_info.error.
D. Use with pytest.raises(ValueError) as exc_info: and then check exc_info.value.

Solution

  1. Step 1: Use pytest.raises with as exc_info

    This syntax captures the exception info object for inspection.
  2. Step 2: Access the exception instance via exc_info.value

    The actual exception object is stored in exc_info.value, allowing attribute checks like the message.
  3. Final Answer:

    Use with pytest.raises(ValueError) as exc_info: and then check exc_info.value. -> Option D
  4. Quick Check:

    Exception captured = exc_info.value [OK]
Hint: Always use 'as exc_info' to access exception details [OK]
Common Mistakes:
  • Not using 'as exc_info' to capture exception
  • Trying to access exc_info.message instead of exc_info.value
  • Using pytest.raises outside a with block
2. Which of the following is the correct syntax to check the exception message text inside a pytest test?
easy
A. assert exc_info.message == 'error occurred'
B. assert exc_info.value.message == 'error occurred'
C. assert str(exc_info.value) == 'error occurred'
D. assert exc_info.error == 'error occurred'

Solution

  1. Step 1: Access exception instance via exc_info.value

    The exception object is stored in exc_info.value.
  2. Step 2: Convert exception to string to get message

    Using str(exc_info.value) returns the exception message text.
  3. Final Answer:

    assert str(exc_info.value) == 'error occurred' -> Option C
  4. Quick Check:

    Exception message = str(exc_info.value) [OK]
Hint: Use str(exc_info.value) to get exception message text [OK]
Common Mistakes:
  • Trying to access exc_info.message which does not exist
  • Using exc_info.error which is invalid
  • Assuming exc_info.value.message always exists
3. Given the code below, what will the assertion check inside the test?
def divide(x, y):
    if y == 0:
        raise ZeroDivisionError('Cannot divide by zero')
    return x / y

def test_divide():
    with pytest.raises(ZeroDivisionError) as exc_info:
        divide(5, 0)
    assert str(exc_info.value) == 'Cannot divide by zero'
medium
A. The test raises an error because exc_info is not defined.
B. The test passes because the exception message matches exactly.
C. The test fails because the exception message is different.
D. The test fails because the exception type is wrong.

Solution

  1. Step 1: The function raises ZeroDivisionError with correct message

    Calling divide(5, 0) raises ZeroDivisionError with message 'Cannot divide by zero'.
  2. Step 2: The test captures exception and asserts message

    Using pytest.raises captures the exception, and str(exc_info.value) matches the expected message.
  3. Final Answer:

    The test passes because the exception message matches exactly. -> Option B
  4. Quick Check:

    Exception type and message match = pass [OK]
Hint: Match exception message string exactly with str(exc_info.value) [OK]
Common Mistakes:
  • Assuming exception message is accessed by exc_info.message
  • Not using 'as exc_info' to capture exception
  • Confusing exception type with message
4. Identify the error in the following pytest test code that checks exception attributes:
def test_error():
    with pytest.raises(ValueError) as exc_info:
        raise ValueError('Invalid input')
    assert exc_info.message == 'Invalid input'
medium
A. Using exc_info.message instead of str(exc_info.value) to check message.
B. Not raising the exception inside the with block.
C. Missing parentheses in pytest.raises call.
D. Using as exc_info outside the with block.

Solution

  1. Step 1: Check how exception message is accessed

    The exception message is not stored in exc_info.message, but must be accessed via str(exc_info.value).
  2. Step 2: Correct assertion syntax

    Replace assert exc_info.message == 'Invalid input' with assert str(exc_info.value) == 'Invalid input'.
  3. Final Answer:

    Using exc_info.message instead of str(exc_info.value) to check message. -> Option A
  4. Quick Check:

    Exception message accessed via str(exc_info.value) [OK]
Hint: Use str(exc_info.value) to get exception message, not exc_info.message [OK]
Common Mistakes:
  • Trying to access exc_info.message which does not exist
  • Not raising exception inside with block
  • Incorrect pytest.raises syntax
5. You want to test a custom exception MyError that has an attribute code. Which pytest code correctly checks both the exception type and the code attribute value?
hard
A. with pytest.raises(MyError) as exc_info: raise MyError('fail', code=404) assert exc_info.value.code == 404
B. with pytest.raises(MyError) as exc_info: raise MyError('fail', code=404) assert exc_info.code == 404
C. with pytest.raises(MyError): raise MyError('fail', code=404) assert exc_info.value.code == 404
D. with pytest.raises(MyError) as exc_info: raise MyError('fail', code=404) assert str(exc_info.value) == 404

Solution

  1. Step 1: Capture exception with as exc_info

    This allows access to the exception instance and its attributes.
  2. Step 2: Access custom attribute code on exc_info.value

    The exception instance is exc_info.value, so exc_info.value.code accesses the attribute.
  3. Final Answer:

    with pytest.raises(MyError) as exc_info: raise MyError('fail', code=404) assert exc_info.value.code == 404 -> Option A
  4. Quick Check:

    Custom attribute checked via exc_info.value.code [OK]
Hint: Check custom attributes on exc_info.value, not exc_info directly [OK]
Common Mistakes:
  • Trying to access exc_info.code instead of exc_info.value.code
  • Not using 'as exc_info' to capture exception
  • Comparing str(exc_info.value) to a number
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard