Bird
Raised Fist0
PyTesttesting~15 mins

Testing custom exceptions in PyTest - Deep Dive

Choose your learning style10 modes available
Overview - Testing custom exceptions
What is it?
Testing custom exceptions means checking that your program correctly raises special error messages you create. These custom exceptions help your code explain problems clearly when something goes wrong. By writing tests for them, you make sure your program behaves as expected in error situations. This helps catch bugs early and improves code reliability.
Why it matters
Without testing custom exceptions, errors might go unnoticed or be misunderstood, causing bigger problems later. Imagine a safety alarm that never sounds when danger appears. Testing ensures your program signals problems clearly and stops bad actions before they cause harm. This builds trust in your software and saves time fixing hidden bugs.
Where it fits
Before testing custom exceptions, you should know basic Python programming and how to write simple tests with pytest. After this, you can learn about advanced error handling, test fixtures, and mocking to test more complex scenarios.
Mental Model
Core Idea
Testing custom exceptions confirms your program raises the right errors at the right time to handle unexpected situations safely.
Think of it like...
It's like checking that a fire alarm sounds only when there is smoke, not when you burn toast. You want to be sure the alarm works correctly to warn you of real danger.
┌─────────────────────────────┐
│       Function runs          │
├─────────────┬───────────────┤
│ Normal case │ Error case    │
│             │               │
│ Returns     │ Raises custom │
│ result      │ exception     │
└─────────────┴───────────────┘
          │
          ▼
┌─────────────────────────────┐
│ pytest test checks if error  │
│ is raised when expected      │
└─────────────────────────────┘
Build-Up - 6 Steps
1
FoundationWhat are custom exceptions
🤔
Concept: Custom exceptions are special error types you create to describe specific problems in your program.
In Python, you create a custom exception by making a new class that inherits from Exception. For example: class MyError(Exception): pass This means MyError is a new kind of error you can raise when something specific goes wrong.
Result
You have a new error type that you can use to signal special problems in your code.
Understanding how to create custom exceptions lets you communicate errors clearly and handle them separately from general errors.
2
FoundationHow to raise custom exceptions
🤔
Concept: You use the raise statement to trigger your custom exception when a problem happens.
Example: def divide(a, b): if b == 0: raise MyError('Cannot divide by zero') return a / b This stops the function and signals the error with your message.
Result
If b is zero, the function raises MyError instead of returning a result.
Knowing when and how to raise custom exceptions helps your program stop bad actions and explain why.
3
IntermediateBasic pytest exception testing
🤔Before reading on: do you think pytest can check if any exception is raised, or only built-in exceptions? Commit to your answer.
Concept: pytest can check if your code raises any exception, including custom ones, using the pytest.raises context manager.
Example test: import pytest def test_divide_by_zero(): with pytest.raises(MyError): divide(10, 0) This test passes only if divide(10, 0) raises MyError.
Result
Test passes if MyError is raised; fails otherwise.
Using pytest.raises lets you confirm your code signals errors exactly as you designed.
4
IntermediateChecking exception messages
🤔Before reading on: do you think pytest.raises can check the error message text automatically? Commit to your answer.
Concept: You can capture the raised exception and check its message to ensure it contains the right information.
Example: with pytest.raises(MyError) as exc_info: divide(10, 0) assert 'divide by zero' in str(exc_info.value) This confirms the error message is correct.
Result
Test passes only if the exception message contains the expected text.
Verifying error messages ensures your exceptions communicate the exact problem clearly.
5
AdvancedTesting exceptions in complex flows
🤔Before reading on: do you think testing exceptions in functions that call others is straightforward or tricky? Commit to your answer.
Concept: When exceptions happen deep inside multiple function calls, tests must trigger the right conditions and catch the exceptions properly.
Example: def process(data): if not data: raise MyError('No data') return divide(10, data) Test: with pytest.raises(MyError): process(0) This tests exceptions raised indirectly through nested calls.
Result
Test passes if MyError is raised anywhere inside process.
Understanding exception flow through multiple calls helps you write tests that catch errors no matter where they happen.
6
ExpertAvoiding false positives in exception tests
🤔Before reading on: do you think any exception raised inside pytest.raises block counts as a pass? Commit to your answer.
Concept: pytest.raises passes if any exception of the expected type is raised, even if it happens earlier than intended. You must write tests carefully to avoid false positives.
Example pitfall: with pytest.raises(MyError): setup() divide(10, 0) If setup() raises MyError, test passes but misses the real test target. Better: with pytest.raises(MyError): divide(10, 0) This isolates the code that should raise the error.
Result
Tests become more precise and reliable, avoiding false passes.
Knowing how pytest.raises works internally prevents tests that pass for the wrong reasons, improving test quality.
Under the Hood
When pytest runs a test with pytest.raises, it temporarily watches for exceptions inside the block. If the expected exception type occurs, pytest catches it and marks the test as passed. If no exception or a different type occurs, the test fails. The exception object is stored so you can inspect its message or attributes.
Why designed this way?
This design lets tests focus on error conditions without crashing the whole test run. It provides a clean, readable way to check errors, improving test clarity and maintainability. Alternatives like try-except blocks are more verbose and error-prone.
┌───────────────────────────────┐
│ pytest test starts             │
├───────────────────────────────┤
│ Enter pytest.raises context    │
│ ┌───────────────────────────┐ │
│ │ Run code block             │ │
│ │ ┌───────────────────────┐ │ │
│ │ │ Exception raised?      │─┤─┤
│ │ └───────────────────────┘ │ │
│ │       Yes                 │ │
│ │       │                  │ │
│ │       ▼                  │ │
│ │ Catch exception if type matches │
│ │ Mark test passed          │ │
│ │ Store exception info      │ │
│ └───────────────────────────┘ │
│ Exit pytest.raises context     │
└───────────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does pytest.raises catch exceptions raised outside its block? Commit to yes or no.
Common Belief:pytest.raises catches any exception raised during the test function, no matter where.
Tap to reveal reality
Reality:pytest.raises only catches exceptions raised inside its own with-block, not outside.
Why it matters:If you expect pytest.raises to catch exceptions outside its block, your tests may miss real errors or give false passes.
Quick: Do you think pytest.raises checks the exception message automatically? Commit to yes or no.
Common Belief:pytest.raises automatically verifies the exception message matches the expected error.
Tap to reveal reality
Reality:pytest.raises only checks the exception type; you must manually assert the message if needed.
Why it matters:Assuming automatic message checking can let wrong error messages slip through tests unnoticed.
Quick: Can pytest.raises detect if the wrong exception type is raised? Commit to yes or no.
Common Belief:pytest.raises passes the test if any exception is raised, regardless of type.
Tap to reveal reality
Reality:pytest.raises only passes if the exact expected exception type (or subclass) is raised.
Why it matters:This prevents tests from passing due to unrelated errors, ensuring precise error handling.
Quick: Do you think catching exceptions with pytest.raises can hide bugs? Commit to yes or no.
Common Belief:Using pytest.raises always improves test quality and never hides bugs.
Tap to reveal reality
Reality:Improper use of pytest.raises can hide bugs if the block is too large or includes setup code that raises exceptions.
Why it matters:Tests may pass incorrectly, giving false confidence and letting bugs reach production.
Expert Zone
1
pytest.raises can be used as a context manager or a function decorator, each with subtle differences in scope and readability.
2
Custom exceptions can carry extra data attributes; testing these requires accessing the exception instance via exc_info.value.
3
Stacked or nested exceptions require careful test design to ensure the correct exception is caught and verified.
When NOT to use
Avoid using pytest.raises for testing exceptions in asynchronous code; instead, use pytest-asyncio's async support or other async test tools. Also, do not use pytest.raises to catch exceptions that should be handled inside the code logic rather than tested externally.
Production Patterns
In real projects, tests for custom exceptions often include checking error messages, error codes, and side effects like logging. Teams use parameterized tests to cover many error cases efficiently. Exception tests are integrated into CI pipelines to catch regressions early.
Connections
Error handling
builds-on
Testing custom exceptions deepens understanding of error handling by verifying not just that errors occur, but that they are the right errors with clear messages.
Defensive programming
supports
Testing exceptions supports defensive programming by ensuring your code fails safely and predictably under bad inputs or states.
Medical diagnostics
analogy in process
Just like doctors test for specific symptoms to diagnose diseases accurately, testing custom exceptions checks for precise error signals to diagnose software problems.
Common Pitfalls
#1Catching exceptions outside the pytest.raises block
Wrong approach:def test_error(): with pytest.raises(MyError): setup() divide(10, 0)
Correct approach:def test_error(): setup() with pytest.raises(MyError): divide(10, 0)
Root cause:Misunderstanding that pytest.raises only watches exceptions inside its block, so exceptions raised earlier are missed.
#2Not checking exception message content
Wrong approach:with pytest.raises(MyError): divide(10, 0)
Correct approach:with pytest.raises(MyError) as exc_info: divide(10, 0) assert 'divide by zero' in str(exc_info.value)
Root cause:Assuming that raising the correct exception type is enough without verifying the error message.
#3Using too large code block inside pytest.raises
Wrong approach:with pytest.raises(MyError): setup() divide(10, 0)
Correct approach:setup() with pytest.raises(MyError): divide(10, 0)
Root cause:Including unrelated code in the block can cause false positives if that code raises the exception.
Key Takeaways
Custom exceptions let your program signal specific problems clearly and safely.
pytest.raises is the main tool to test that your code raises these exceptions correctly.
Always check both the exception type and its message to ensure precise error handling.
Write tests that isolate the code raising exceptions to avoid false positives.
Understanding how pytest.raises works internally helps you write reliable and maintainable tests.

Practice

(1/5)
1. What is the main purpose of using pytest.raises() in testing custom exceptions?
easy
A. To check if a specific exception is raised during a function call
B. To skip a test when an exception occurs
C. To automatically fix exceptions in the code
D. To print the exception message to the console

Solution

  1. Step 1: Understand the role of pytest.raises()

    This function is used to verify that a specific exception is raised when running a block of code.
  2. Step 2: Identify the purpose in testing

    It helps confirm that error handling works as expected by catching the intended exception type.
  3. Final Answer:

    To check if a specific exception is raised during a function call -> Option A
  4. Quick Check:

    pytest.raises() checks exceptions [OK]
Hint: Remember: pytest.raises() tests if code throws an error [OK]
Common Mistakes:
  • Thinking pytest.raises() fixes errors
  • Using pytest.raises() to skip tests
  • Expecting pytest.raises() to print messages
2. Which of the following is the correct syntax to test if a function foo() raises a custom exception MyError using pytest?
easy
A. assert pytest.raises(MyError, foo)
B. pytest.raises(MyError, foo())
C. with pytest.raises(MyError): foo()
D. pytest.check_exception(MyError, foo())

Solution

  1. Step 1: Recall pytest.raises() usage

    The correct way is to use it as a context manager with with and call the function inside the block.
  2. Step 2: Analyze each option

    with pytest.raises(MyError): foo() uses the context manager correctly. Options B and C misuse the function call or assert. pytest.check_exception(MyError, foo()) uses a non-existent function.
  3. Final Answer:

    with pytest.raises(MyError): foo() -> Option C
  4. Quick Check:

    Use context manager with pytest.raises() [OK]
Hint: Use 'with pytest.raises(Exception):' before calling function [OK]
Common Mistakes:
  • Calling function inside pytest.raises() without 'with'
  • Using assert with pytest.raises() incorrectly
  • Using non-existent pytest functions
3. Given the code below, what will be the test result?
class MyError(Exception):
    pass

def func():
    raise MyError("Error happened")

def test_func():
    with pytest.raises(MyError) as exc_info:
        func()
    assert str(exc_info.value) == "Error happened"
medium
A. Test raises a syntax error
B. Test fails because the exception type is wrong
C. Test fails because the exception message is incorrect
D. Test passes because the exception and message match

Solution

  1. Step 1: Check exception type raised by func()

    func() raises MyError with message "Error happened".
  2. Step 2: Verify test assertions

    The test uses pytest.raises(MyError) to catch the exception and asserts the message matches exactly.
  3. Final Answer:

    Test passes because the exception and message match -> Option D
  4. Quick Check:

    Exception type and message match = pass [OK]
Hint: Match both exception type and message for success [OK]
Common Mistakes:
  • Ignoring exception message in assertion
  • Catching wrong exception type
  • Forgetting to call the function inside the context
4. Identify the error in the following pytest test for a custom exception:
def test_error():
    with pytest.raises(ValueError):
        raise TypeError("Wrong type")
medium
A. The test will pass because any exception is accepted
B. The test will fail because the raised exception type does not match the expected
C. The test has a syntax error due to missing colon
D. The test will raise an error because ValueError is not imported

Solution

  1. Step 1: Compare expected and raised exceptions

    The test expects a ValueError but raises a TypeError instead.
  2. Step 2: Understand pytest.raises() behavior

    pytest.raises() fails the test if the raised exception type does not match the expected one.
  3. Final Answer:

    The test will fail because the raised exception type does not match the expected -> Option B
  4. Quick Check:

    Exception type mismatch causes test failure [OK]
Hint: Expected and raised exception types must match exactly [OK]
Common Mistakes:
  • Assuming any exception passes the test
  • Ignoring exception type differences
  • Confusing syntax errors with logic errors
5. You have a custom exception MyError that can be raised with different messages. How can you write a pytest test that checks the exception is raised and the message contains the word "timeout" regardless of other text?
hard
A. Use with pytest.raises(MyError, match="timeout"): to match substring
B. Use with pytest.raises(MyError) as e: then assert str(e.value) == "timeout"
C. Use assertRaises(MyError, "timeout") to check message
D. Use pytest.raises(MyError, message_contains="timeout")

Solution

  1. Step 1: Recall pytest.raises() match parameter

    pytest.raises() accepts a match argument to check if the exception message matches a regex or substring.
  2. Step 2: Compare options for message checking

    with pytest.raises(MyError, match="timeout"): correctly uses the match parameter to verify the message contains "timeout". with pytest.raises(MyError) as e: followed by assert str(e.value) == "timeout" only checks for exact match, failing if there is extra text. Options C and D use invalid syntax.
  3. Final Answer:

    Use with pytest.raises(MyError, match="timeout"): -> Option A
  4. Quick Check:

    Use match parameter to check message substring [OK]
Hint: Use pytest.raises(..., match="text") to check message content [OK]
Common Mistakes:
  • Using exact match instead of substring check for the message
  • Using non-existent pytest.raises() parameters
  • Not checking message content at all
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