Skip to content

Commit 66ce1cb

Browse files
RJPercivalclaude
andcommitted
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]>
1 parent c6730fb commit 66ce1cb

File tree

3 files changed

+82
-1
lines changed

3 files changed

+82
-1
lines changed

README.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,34 @@ the ``assert_all_requests_are_fired`` value:
917917
content_type="application/json",
918918
)
919919
920+
By default, when an exception occurs within the context manager, the assertion
921+
about unfired requests is suppressed to avoid masking the original exception.
922+
However, this can hide valuable context about which mocked requests were or weren't
923+
called. You can use ``assert_on_exception=True`` to always see this information when
924+
debugging test failures.
925+
926+
.. code-block:: python
927+
928+
import responses
929+
import requests
930+
931+
932+
def test_with_assert_on_exception():
933+
with responses.RequestsMock(
934+
assert_all_requests_are_fired=True, assert_on_exception=True
935+
) as rsps:
936+
rsps.add(responses.GET, "http://example.com/users", body="test")
937+
rsps.add(responses.GET, "http://example.com/profile", body="test")
938+
requests.get("http://example.com/users")
939+
raise ValueError("Something went wrong")
940+
941+
# Output:
942+
# ValueError: Something went wrong
943+
#
944+
# During handling of the above exception, another exception occurred:
945+
#
946+
# AssertionError: Not all requests have been executed [('GET', 'http://example.com/profile')]
947+
920948
Assert Request Call Count
921949
-------------------------
922950

responses/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,11 +730,13 @@ def __init__(
730730
registry: Type[FirstMatchRegistry] = FirstMatchRegistry,
731731
*,
732732
real_adapter_send: "_HTTPAdapterSend" = _real_send,
733+
assert_on_exception: bool = False,
733734
) -> None:
734735
self._calls: CallList = CallList()
735736
self.reset()
736737
self._registry: FirstMatchRegistry = registry() # call only after reset
737738
self.assert_all_requests_are_fired: bool = assert_all_requests_are_fired
739+
self.assert_on_exception: bool = assert_on_exception
738740
self.response_callback: Optional[Callable[[Any], Response]] = response_callback
739741
self.passthru_prefixes: Tuple[_URLPatternType, ...] = tuple(passthru_prefixes)
740742
self.target: str = target
@@ -993,7 +995,7 @@ def __enter__(self) -> "RequestsMock":
993995
def __exit__(self, type: Any, value: Any, traceback: Any) -> None:
994996
success = type is None
995997
try:
996-
self.stop(allow_assert=success)
998+
self.stop(allow_assert=success or self.assert_on_exception)
997999
finally:
9981000
self.reset()
9991001

responses/tests/test_responses.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,57 @@ def test_some_second_function():
12171217
assert_reset()
12181218

12191219

1220+
def test_assert_on_exception():
1221+
"""Test that assert_on_exception controls assertion behavior during exceptions."""
1222+
1223+
def run():
1224+
# Default behavior (assert_on_exception=False):
1225+
# assertion should NOT be raised when an exception occurs
1226+
with pytest.raises(ValueError) as value_exc_info:
1227+
with responses.RequestsMock(
1228+
assert_all_requests_are_fired=True, assert_on_exception=False
1229+
) 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+
# Should only see the ValueError, not the AssertionError about unfired requests
1236+
assert "Main error" in str(value_exc_info.value)
1237+
assert "not-called.com" not in str(value_exc_info.value)
1238+
1239+
# With assert_on_exception=True: assertion WILL be raised even with an exception
1240+
# The AssertionError will be the primary exception, with the ValueError as context
1241+
with pytest.raises(AssertionError) as assert_exc_info:
1242+
with responses.RequestsMock(
1243+
assert_all_requests_are_fired=True, assert_on_exception=True
1244+
) as m:
1245+
m.add(responses.GET, "http://example.com", body=b"test")
1246+
m.add(responses.GET, "http://not-called.com", body=b"test")
1247+
requests.get("http://example.com")
1248+
raise ValueError("Main error")
1249+
1250+
# The AssertionError should mention the unfired request
1251+
assert "not-called.com" in str(assert_exc_info.value)
1252+
# Python automatically chains exceptions, so we should see both in the traceback
1253+
assert isinstance(assert_exc_info.value.__context__, ValueError)
1254+
assert "Main error" in str(assert_exc_info.value.__context__)
1255+
1256+
# Test that both work normally when no other exception occurs
1257+
with pytest.raises(AssertionError) as assert_exc_info2:
1258+
with responses.RequestsMock(
1259+
assert_all_requests_are_fired=True, assert_on_exception=True
1260+
) as m:
1261+
m.add(responses.GET, "http://example.com", body=b"test")
1262+
m.add(responses.GET, "http://not-called.com", body=b"test")
1263+
requests.get("http://example.com")
1264+
1265+
assert "not-called.com" in str(assert_exc_info2.value)
1266+
1267+
run()
1268+
assert_reset()
1269+
1270+
12201271
def test_allow_redirects_samehost():
12211272
redirecting_url = "http://example.com"
12221273
final_url_path = "/1"

0 commit comments

Comments
 (0)