Skip to content

Commit 051b79e

Browse files
RJPercivalclaudemarkstory
authored
Make assert_all_requests_are_fired always assert on exception (#782)
* Add assert_on_exception parameter to RequestsMock 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 <[email protected]> * Add decorator support for assert_on_exception 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 <[email protected]> * Update CHANGES for assert_on_exception feature 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 <[email protected]> * Always raise assertions on exception when assert_all_requests_are_fired=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 <[email protected]> --------- Co-authored-by: Claude <[email protected]> Co-authored-by: Mark Story <[email protected]>
1 parent 0905cb8 commit 051b79e

File tree

4 files changed

+100
-10
lines changed

4 files changed

+100
-10
lines changed

CHANGES

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1-
0.x.y
2-
-----
1+
0.26.0
2+
------
33

4+
* When using `assert_all_requests_are_fired=True`, assertions about
5+
unfired requests are now raised even when an exception occurs in the context manager or
6+
decorated function. Previously, these assertions were suppressed when exceptions occurred.
7+
This new behavior provides valuable debugging context about which mocked requests were
8+
or weren't called.
49
* Consider the `Retry-After` header when handling retries
510

11+
612
0.25.8
713
------
814

README.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,31 @@ the ``assert_all_requests_are_fired`` value:
917917
content_type="application/json",
918918
)
919919
920+
When ``assert_all_requests_are_fired=True`` and an exception occurs within the
921+
context manager, assertions about unfired requests will still be raised. This
922+
provides valuable context about which mocked requests were or weren't called
923+
when debugging test failures.
924+
925+
.. code-block:: python
926+
927+
import responses
928+
import requests
929+
930+
931+
def test_with_exception():
932+
with responses.RequestsMock(assert_all_requests_are_fired=True) as rsps:
933+
rsps.add(responses.GET, "http://example.com/users", body="test")
934+
rsps.add(responses.GET, "http://example.com/profile", body="test")
935+
requests.get("http://example.com/users")
936+
raise ValueError("Something went wrong")
937+
938+
# Output:
939+
# ValueError: Something went wrong
940+
#
941+
# During handling of the above exception, another exception occurred:
942+
#
943+
# AssertionError: Not all requests have been executed [('GET', 'http://example.com/profile')]
944+
920945
Assert Request Call Count
921946
-------------------------
922947

responses/__init__.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,8 @@ def wrapper(*args: Any, **kwargs: Any) -> Any: # type: ignore[misc]
226226
responses._set_registry(registry)
227227

228228
with assert_mock, responses:
229-
# set 'assert_all_requests_are_fired' temporarily for a single run.
230-
# Mock automatically unsets to avoid leakage to another decorated
229+
# set 'assert_all_requests_are_fired' temporarily for a
230+
# single run. Mock automatically unsets to avoid leakage to another decorated
231231
# function since we still apply the value on 'responses.mock' object
232232
return func(*args, **kwargs)
233233

@@ -991,9 +991,8 @@ def __enter__(self) -> "RequestsMock":
991991
return self
992992

993993
def __exit__(self, type: Any, value: Any, traceback: Any) -> None:
994-
success = type is None
995994
try:
996-
self.stop(allow_assert=success)
995+
self.stop(allow_assert=True)
997996
finally:
998997
self.reset()
999998

@@ -1008,8 +1007,7 @@ def activate(
10081007
registry: Type[Any] = ...,
10091008
assert_all_requests_are_fired: bool = ...,
10101009
) -> Callable[["_F"], "_F"]:
1011-
"""Overload for scenario when
1012-
'responses.activate(registry=, assert_all_requests_are_fired=True)' is used.
1010+
"""Overload for scenario when 'responses.activate(...)' is used.
10131011
See https://github.com/getsentry/responses/pull/469 for more details
10141012
"""
10151013

responses/tests/test_responses.py

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,11 +1163,13 @@ def run():
11631163
with responses.RequestsMock() as m:
11641164
m.add(responses.GET, "http://example.com", body=b"test")
11651165

1166-
# check that assert_all_requests_are_fired doesn't swallow exceptions
1167-
with pytest.raises(ValueError):
1166+
# check that assert_all_requests_are_fired raises assertions even with exceptions
1167+
with pytest.raises(AssertionError) as exc_info:
11681168
with responses.RequestsMock() as m:
11691169
m.add(responses.GET, "http://example.com", body=b"test")
11701170
raise ValueError()
1171+
# The ValueError should be chained as the context
1172+
assert isinstance(exc_info.value.__context__, ValueError)
11711173

11721174
# check that assert_all_requests_are_fired=True doesn't remove urls
11731175
with responses.RequestsMock(assert_all_requests_are_fired=True) as m:
@@ -1217,6 +1219,65 @@ def test_some_second_function():
12171219
assert_reset()
12181220

12191221

1222+
def test_assert_all_requests_are_fired_during_exception():
1223+
"""Test that assertions are raised even when an exception occurs."""
1224+
1225+
def run():
1226+
# Assertions WILL be raised even with an exception
1227+
# The AssertionError will be the primary exception, with the ValueError as context
1228+
with pytest.raises(AssertionError) as assert_exc_info:
1229+
with responses.RequestsMock(assert_all_requests_are_fired=True) as m:
1230+
m.add(responses.GET, "http://example.com", body=b"test")
1231+
m.add(responses.GET, "http://not-called.com", body=b"test")
1232+
requests.get("http://example.com")
1233+
raise ValueError("Main error")
1234+
1235+
# The AssertionError should mention the unfired request
1236+
assert "not-called.com" in str(assert_exc_info.value)
1237+
# Python automatically chains exceptions, so we should see both in the traceback
1238+
assert isinstance(assert_exc_info.value.__context__, ValueError)
1239+
assert "Main error" in str(assert_exc_info.value.__context__)
1240+
1241+
# Test that it also works normally when no other exception occurs
1242+
with pytest.raises(AssertionError) as assert_exc_info2:
1243+
with responses.RequestsMock(assert_all_requests_are_fired=True) as m:
1244+
m.add(responses.GET, "http://example.com", body=b"test")
1245+
m.add(responses.GET, "http://not-called.com", body=b"test")
1246+
requests.get("http://example.com")
1247+
1248+
assert "not-called.com" in str(assert_exc_info2.value)
1249+
1250+
run()
1251+
assert_reset()
1252+
1253+
1254+
def test_assert_all_requests_are_fired_during_exception_with_decorator():
1255+
"""Test that assertions are raised even when an exception occurs.
1256+
1257+
This tests the behavior with the @responses.activate decorator.
1258+
"""
1259+
1260+
# Assertions WILL be raised even with an exception when using the decorator
1261+
with pytest.raises(AssertionError) as assert_exc_info:
1262+
1263+
@responses.activate(assert_all_requests_are_fired=True)
1264+
def test_with_exception():
1265+
responses.add(responses.GET, "http://example.com", body=b"test")
1266+
responses.add(responses.GET, "http://not-called.com", body=b"test")
1267+
requests.get("http://example.com")
1268+
raise ValueError("Main error")
1269+
1270+
test_with_exception()
1271+
1272+
# The AssertionError should mention the unfired request
1273+
assert "not-called.com" in str(assert_exc_info.value)
1274+
# Python automatically chains exceptions, so we should see both in the traceback
1275+
assert isinstance(assert_exc_info.value.__context__, ValueError)
1276+
assert "Main error" in str(assert_exc_info.value.__context__)
1277+
1278+
assert_reset()
1279+
1280+
12201281
def test_allow_redirects_samehost():
12211282
redirecting_url = "http://example.com"
12221283
final_url_path = "/1"

0 commit comments

Comments
 (0)