Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›pytest & Testing›Testing Exceptions — pytest.raises

Advanced Testing

Testing Exceptions — pytest.raises

The pytest.raises mechanism is a specialized context manager designed to assert that a specific block of code raises a defined exception. It is essential for verifying error-handling logic and ensuring that applications fail predictably under invalid input or unexpected conditions. You should employ this tool whenever you need to confirm that your defensive programming practices effectively capture and raise the correct error types.

The Basics of Exception Assertion

When writing robust software, ensuring that your functions fail gracefully is just as critical as ensuring they succeed under normal conditions. The `pytest.raises` context manager provides a declarative way to verify that a block of code execution triggers a specific exception type. By wrapping the code that should trigger an error within this block, the test will pass only if that specific exception is raised. If the code completes without an exception, or if it raises a different type of error, the test fails. This works because pytest intercepts the exception signal internally; if the signal matches the provided exception type, it suppresses the error, allowing the test to continue. This pattern is fundamental to testing validation logic where you deliberately pass incorrect arguments to ensure your system identifies them correctly instead of failing silently or producing corrupted state.

import pytest

def divide(a, b):
    if b == 0:
        # We expect a ZeroDivisionError here
        raise ZeroDivisionError("Cannot divide by zero")
    return a / b

def test_divide_by_zero():
    # Assert that calling divide with 0 raises the error
    with pytest.raises(ZeroDivisionError):
        divide(10, 0)

Validating Exception Messages

Simply checking the type of an exception is often insufficient, as many functions might raise the same type of error for different reasons. To make your tests more precise, you should use the match argument within the `pytest.raises` constructor. This argument accepts a string or a regular expression that is checked against the exception object's string representation. By verifying the error message, you ensure that your code is failing for the specific, intended reason rather than some unexpected side effect. This is particularly important for library code where providing helpful, accurate error messages to the user is a requirement. If the exception is raised but the message does not match your regex, the test will fail, alerting you that your error handling might be reporting the wrong issue to the end user or that the implementation has changed.

import pytest

def set_age(age):
    if age < 0:
        raise ValueError(f"Age {age} is invalid")
    return age

def test_set_age_invalid():
    # Validate the message matches the expected format
    with pytest.raises(ValueError, match=r"Age -5 is invalid"):
        set_age(-5)

Extracting the Exception Object

Sometimes you need to inspect the attributes of the caught exception to perform deeper validation beyond the type and the message. By using the 'as' syntax, you can capture the exception instance into a variable within the context manager's scope. This exception info object, which is of type ExceptionInfo, contains metadata about the captured error, including the message, the traceback, and the raw exception object. This is useful when you want to access custom attributes of a user-defined exception or check properties that are set during the exception's initialization. Once captured, the exception info allows you to assert on any property of the exception object, providing granular control over your test verification. This is especially powerful when dealing with complex custom error classes that encapsulate specific state information relevant to the failure of the operation.

import pytest

class CustomError(Exception):
    def __init__(self, code):
        self.code = code

def trigger():
    raise CustomError(404)

def test_custom_exception():
    # Capture the exception object to inspect its properties
    with pytest.raises(CustomError) as excinfo:
        trigger()
    assert excinfo.value.code == 404

Handling Exceptions in Setup and Teardown

While `pytest.raises` is commonly used within test functions, its behavior is fundamentally tied to how pytest manages the call stack. Because it works as a context manager, it must be placed directly inside the function scope where the error is expected to occur. You cannot easily wrap external setup or teardown fixtures in a traditional block within a test; instead, if you are testing that a fixture itself raises an error, you must utilize the pytest mechanism in a way that respects the scope. If an error is expected to occur before your test even begins, you might need to reconsider your test structure or use internal logic within the setup. However, for most functional testing, keeping the `pytest.raises` block tightly scoped around the operation under test is the best practice to avoid false positives and maintain test readability.

import pytest

def setup_database():
    # If connection fails during setup
    raise ConnectionError("Failed to connect")

def test_fixture_logic():
    # Assert the fixture-like behavior fails correctly
    with pytest.raises(ConnectionError):
        setup_database()

Avoiding Common Pitfalls

A common mistake when using `pytest.raises` is wrapping too much code inside the context manager. If you place code that does not throw an exception inside the block, and then something late in that block throws an unexpected exception, you might accidentally mask bugs or misunderstand which line actually triggered the failure. It is essential to keep the code block inside the context manager as small and focused as possible, ideally containing only the single function call that is expected to fail. Additionally, always ensure that your assertions regarding exceptions are specific; catching broad exceptions like 'Exception' or 'BaseException' can lead to brittle tests that pass when they should fail because they accidentally swallowed an unexpected, unrelated error. By being precise, you guarantee that your tests are verifying the specific contract of your function.

import pytest

def risky_process(x):
    # Only this line should be in the context
    return 10 / x

def test_risky_process_isolated():
    # Precise scoping ensures clarity in failure
    with pytest.raises(ZeroDivisionError):
        risky_process(0)

Key points

  • The pytest.raises context manager confirms that a specific code block raises an expected exception.
  • If no exception is raised, or if the wrong type is raised, the test will fail.
  • You can use the match parameter to verify that the exception message matches a specific string or regex.
  • Using the as syntax allows you to capture the exception object to perform assertions on its internal attributes.
  • It is best practice to keep the code block inside the context manager minimal and focused on the operation being tested.
  • Catching generic exception types can hide bugs and lead to false positives in your test suite.
  • ExceptionInfo objects provide deep access to metadata, tracebacks, and state within the caught exception.
  • Proper use of exception testing ensures that your application handles invalid inputs according to its architectural contract.

Common mistakes

  • Mistake: Asserting the exception inside the 'with' block after it has been raised. Why it's wrong: Code execution stops at the point of the raised exception, so any assertions written after the error line in the same block are never executed. Fix: Perform assertions only after the 'with pytest.raises' block or use 'match' within the context manager.
  • Mistake: Misunderstanding that 'pytest.raises' captures exceptions to pass the test. Why it's wrong: Developers often think they need a try/except block. pytest.raises acts as the handler. Fix: Simply use the context manager without manual try/except blocks.
  • Mistake: Forgetting to verify the exception message. Why it's wrong: Testing only the exception type can lead to false positives if the wrong exception with the same type is raised. Fix: Use the 'match' parameter or inspect the exception info object to verify the message content.
  • Mistake: Placing code that doesn't raise an exception inside the block. Why it's wrong: If the code inside the block executes successfully, the test will fail because no exception occurred. Fix: Keep the 'with' block as small as possible, containing only the line expected to trigger the exception.
  • Mistake: Relying on generic 'Exception'. Why it's wrong: This masks potential bugs where a different, unexpected exception is raised by the code. Fix: Always specify the most granular, specific exception type possible.

Interview questions

What is the basic purpose of the pytest.raises context manager in a test suite?

The pytest.raises context manager is used to assert that a specific block of code raises a particular exception during execution. In testing, it is crucial to verify not only that code succeeds under normal conditions but also that it fails gracefully when encountering invalid input or unexpected states. By using 'with pytest.raises(ExpectedException):', you ensure that your code correctly enforces constraints, preventing the test from failing when an error is intentionally triggered.

How do you capture the exception object itself when using pytest.raises to perform further assertions on the error message?

To inspect the details of an exception, you assign the result of the pytest.raises context manager to a variable using the 'as' keyword. For example, 'with pytest.raises(ValueError) as exc_info:'. This 'exc_info' object holds a wealth of information, specifically within its 'value' attribute. You can then perform assertions like 'assert "invalid input" in str(exc_info.value)' to verify that the error message contains the expected diagnostic information, which is vital for debugging.

Explain the importance of the 'match' parameter in pytest.raises and why you should use it.

The 'match' parameter is an incredibly useful feature that allows you to provide a regex string to verify the exception message concurrently with the exception type. Relying solely on the exception class is often insufficient because a single function might raise the same exception type for different reasons. By using 'pytest.raises(ValueError, match="must be positive")', you ensure the code failed for the specific reason you intended, preventing false positives where the wrong error was raised.

Compare the two approaches: using pytest.raises as a context manager versus using it as a function decorator.

The context manager approach (using the 'with' block) is the standard and preferred way to test exceptions because it allows you to pinpoint exactly which line of code should trigger the failure. Conversely, pytest.raises cannot be used as a function decorator; it is exclusively a context manager. Attempting to use it as a decorator would fail, so developers should always encapsulate the specific failing call within the block to maintain clean, readable, and highly precise test cases.

What happens if a test block using pytest.raises fails to raise any exception, or raises the wrong type of exception?

If you use pytest.raises for a specific exception type and that exception is never raised, pytest will fail the test and report that an exception was expected but not caught. Similarly, if the code raises a different type of exception than the one specified, pytest will not suppress it, causing the test to fail with an error traceback. This is the desired behavior, as it confirms your safety checks are actually functioning as documented.

How would you test for an exception occurring in a scenario where you also need to ensure that some cleanup code still runs regardless of the outcome?

When testing for exceptions that might impact system state, it is common to combine pytest.raises with finalization logic. You should wrap the triggering call inside the pytest.raises block, and place any necessary cleanup code—like closing a file or resetting a database flag—inside a 'finally' block or using a fixture with 'yield'. This ensures that even if an exception occurs, the test environment remains pristine, preventing side effects from leaking into other tests.

All pytest & Testing interview questions →

Check yourself

1. What happens if a block of code inside 'with pytest.raises(ValueError):' executes successfully without raising any error?

  • A.The test passes automatically.
  • B.The test fails because no exception was raised.
  • C.The test reports an unexpected exception error.
  • D.The test hangs indefinitely.
Show answer

B. The test fails because no exception was raised.
If no exception is raised, pytest explicitly fails the test. Option 0 is wrong because a failure is required. Option 2 is wrong because no exception occurred. Option 3 is wrong as pytest handles the termination.

2. Which of the following is the correct way to capture an exception object for further inspection?

  • A.with pytest.raises(ZeroDivisionError) as exc_info:
  • B.with pytest.raises(ZeroDivisionError, capture=True) as exc_info:
  • C.exc_info = pytest.raises(ZeroDivisionError)
  • D.with pytest.raises(ZeroDivisionError) -> exc_info:
Show answer

A. with pytest.raises(ZeroDivisionError) as exc_info:
Using 'as' assigns an ExceptionInfo object to the variable, which allows checking the message or traceback. Options 1, 2, and 3 use incorrect syntax for the pytest API.

3. You want to ensure that a function raises a 'RuntimeError' with the message 'Connection failed'. How do you do this properly?

  • A.with pytest.raises(RuntimeError, message='Connection failed'):
  • B.with pytest.raises(RuntimeError, match='Connection failed'):
  • C.with pytest.raises(RuntimeError): assert 'Connection failed' in str(e)
  • D.with pytest.raises(RuntimeError): assert_message('Connection failed')
Show answer

B. with pytest.raises(RuntimeError, match='Connection failed'):
The 'match' parameter uses regex to check the exception message. Option 0 is a non-existent parameter. Option 2 is inefficient compared to 'match', and Option 3 is not a standard pytest function.

4. Why is it best practice to keep the code inside the 'with pytest.raises' block minimal?

  • A.To improve execution speed by a few milliseconds.
  • B.To avoid capturing exceptions that occur in unrelated code lines.
  • C.To make the test pass faster in parallel mode.
  • D.Because pytest cannot handle more than 5 lines in a block.
Show answer

B. To avoid capturing exceptions that occur in unrelated code lines.
If you put too much code in the block, you might accidentally catch an exception you didn't intend to test. Options 0, 2, and 3 are incorrect as they do not relate to the logic of exception scoping.

5. When using 'exc_info = pytest.raises(...):', how do you access the exception instance itself?

  • A.exc_info.exception
  • B.exc_info.value
  • C.exc_info.get_instance()
  • D.exc_info.error
Show answer

B. exc_info.value
In pytest, the ExceptionInfo object stores the actual exception instance in the '.value' attribute. The other attributes listed do not exist or are not the correct way to retrieve the exception instance.

Take the full pytest & Testing quiz →

← PreviousSide Effects and Return ValuesNext →Testing Async Code — pytest-asyncio

pytest & Testing

25 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app