Skip to content

Conversation

@RJPercival
Copy link

@RJPercival RJPercival commented Nov 3, 2025

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update
  • Other

Description

This PR modifies the behavior of assert_all_requests_are_fired to always raise assertions about unfired requests, even when an exception occurs in the context manager or decorated function. This is a breaking change that improves debugging capabilities by providing complete context about both the original failure and the state of mocked requests.

Background

Previously, when a test failed within the scope of RequestsMock (e.g. an exception was raised or an assertion failed), responses would skip asserting that all requests were fired to avoid masking the original exception. This could hide useful debugging information, as the test failure may have been a side effect of an expected request not being called (e.g. the mocked URL was wrong).

An earlier iteration of this PR introduced an assert_on_exception parameter to make this behavior configurable. However, after further consideration, the new behavior is so valuable for debugging that it should be the default and only behavior.

Key Changes

  • Breaking Change: When assert_all_requests_are_fired=True, assertions about unfired requests are now always raised, even when an exception occurs
  • Exception Chaining: The original exception is preserved as context, so developers see both the original error and which requests weren't called
  • Simplified API: Removed the assert_on_exception parameter - the improved behavior is now automatic
  • Enhanced Debugging: Developers always get complete information about mocked request state during failures

Exception Handling Example

Before (old behavior):

with responses.RequestsMock(assert_all_requests_are_fired=True) as rsps:
    rsps.add(responses.GET, "http://example.com/users", body="test")
    rsps.add(responses.GET, "http://example.com/profile", body="test")  # Not called
    requests.get("http://example.com/users")
    raise ValueError("Something went wrong")

# Output: Only shows ValueError, unfired request info is lost
# ValueError: Something went wrong

After (new behavior):

with responses.RequestsMock(assert_all_requests_are_fired=True) as rsps:
    rsps.add(responses.GET, "http://example.com/users", body="test")
    rsps.add(responses.GET, "http://example.com/profile", body="test")  # Not called
    requests.get("http://example.com/users")
    raise ValueError("Something went wrong")

# Output: Shows both exceptions with proper chaining
# ValueError: Something went wrong
#
# During handling of the above exception, another exception occurred:
#
# AssertionError: Not all requests have been executed [('GET', 'http://example.com/profile')]

Benefits

  • Complete Debug Context: Always see both the original error and unfired request information
  • Exception Chaining: Python's built-in exception chaining preserves full error context
  • Simplified API: No additional parameters needed - the behavior "just works"
  • Better Test Reliability: Failures provide maximum debugging information

Important

This is a breaking change. Code that previously relied on unfired request assertions being suppressed during exceptions will now see those assertions raised. However, this change improves debugging capabilities significantly.

Related Issues

PR checklist

Before submitting this pull request, I have done the following:

Added/updated tests?

Current repository has 100% test coverage.

  • Yes
  • No, and this is why:
  • I need help with writing tests

Test Coverage: Comprehensive tests updated to verify:

  • Context manager behavior with assertions raised during exceptions
  • Decorator functionality with new behavior
  • Exception chaining verification
  • Documentation examples work as expected

When set to True, assertions about unfired requests will be raised even
when an exception occurs in the context manager. This provides valuable
debugging context about which mocked requests were or weren't called
when debugging test failures.

By default (assert_on_exception=False), the assertion is suppressed to
avoid masking the original exception.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@RJPercival RJPercival changed the title Assert on exception Add assert_on_exception parameter to add context to test failures Nov 3, 2025
@RJPercival RJPercival marked this pull request as ready for review November 3, 2025 11:15
@RJPercival RJPercival requested a review from markstory as a code owner November 3, 2025 11:15
raise ValueError("Main error")

# The AssertionError should mention the unfired request
assert "not-called.com" in str(assert_exc_info.value)

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization

The string [not-called.com](1) may be at an arbitrary position in the sanitized URL.
m.add(responses.GET, "http://not-called.com", body=b"test")
requests.get("http://example.com")

assert "not-called.com" in str(assert_exc_info2.value)

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization

The string [not-called.com](1) may be at an arbitrary position in the sanitized URL.
test_with_assert_on_exception()

# The AssertionError should mention the unfired request
assert "not-called.com" in str(assert_exc_info.value)

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization

The string [not-called.com](1) may be at an arbitrary position in the sanitized URL.
Comment on lines 1226 to 1233
with pytest.raises(ValueError) as value_exc_info:
with responses.RequestsMock(
assert_all_requests_are_fired=True, assert_on_exception=False
) as m:
m.add(responses.GET, "http://example.com", body=b"test")
m.add(responses.GET, "http://not-called.com", body=b"test")
requests.get("http://example.com")
raise ValueError("Main error")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is arguable that this behaviour is a bug. And that assert_on_exception doesn't need to exist.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem with changing this behaviour without an opt-in feature flag is that it'd be a breaking change, as the tests demonstrate (the exception leaving the RequestsMock block would change).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree that the exception type changing because the library has become more strict/correct constitutes a breaking change. Following that line of thinking would imply that no additional exception types could be added as they could 'break' compatibility.

Copy link
Author

@RJPercival RJPercival Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's your library, happy to make this a flagless change if you prefer 🙂 I'll go ahead and update the PR accordingly.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

RJPercival and others added 2 commits November 4, 2025 10:52
The @responses.activate decorator now accepts an assert_on_exception
parameter, providing a convenient way to enable assertion checking
even when exceptions occur:

    @responses.activate(
        assert_all_requests_are_fired=True,
        assert_on_exception=True
    )
    def test_my_api():
        ...

This is consistent with the existing decorator support for
assert_all_requests_are_fired and registry parameters.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Document the new assert_on_exception parameter in version 0.26.0.

This is a minor version bump (not patch) because we're adding new
functionality to the public API, even though it's fully backward
compatible with existing code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
…ed=True

This is a breaking change that modifies the behavior of assert_all_requests_are_fired
to always raise assertions about unfired requests even when an exception occurs in
the context manager or decorated function.

Previously, when an exception occurred, assertions about unfired requests were
suppressed to avoid masking the original exception. However, this behavior hid
valuable debugging context about which mocked requests were or weren't called.

The new behavior always raises assertions (when assert_all_requests_are_fired=True),
with the original exception chained as context. This provides developers with complete
information about both the original failure and the state of mocked requests.

Changes:
- Updated __exit__ to always pass allow_assert=True to stop()
- Removed conditional logic that suppressed assertions on exception
- Updated tests to verify assertions are raised during exceptions
- Updated documentation to reflect new behavior

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@RJPercival RJPercival changed the title Add assert_on_exception parameter to add context to test failures Remove assert_on_exception and make assert_all_requests_are_fired always assert on exception Nov 13, 2025
@RJPercival RJPercival changed the title Remove assert_on_exception and make assert_all_requests_are_fired always assert on exception Make assert_all_requests_are_fired always assert on exception Nov 13, 2025
@RJPercival RJPercival requested a review from markstory November 13, 2025 23:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants