Skip to content

Commit 71e55de

Browse files
authored
Raise custom error types for assertion checks (#174)
* Raise custom error types for assertion checks * Fail fast in assert_all_called
1 parent 9627333 commit 71e55de

File tree

6 files changed

+37
-28
lines changed

6 files changed

+37
-28
lines changed

respx/mocks.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import httpx
88
from httpcore import AsyncIteratorByteStream, IteratorByteStream
99

10-
from .models import PassThrough
10+
from .models import AllMockedAssertionError, PassThrough
1111
from .transports import TryTransport
1212

1313
if TYPE_CHECKING:
@@ -105,33 +105,33 @@ def restart(cls) -> None:
105105
@classmethod
106106
def handler(cls, httpx_request):
107107
httpx_response = None
108-
error = None
108+
assertion_error = None
109109
for router in cls.routers:
110110
try:
111111
httpx_response = router.handler(httpx_request)
112-
except AssertionError as e:
113-
error = e.args[0]
112+
except AllMockedAssertionError as error:
113+
assertion_error = error
114114
continue
115115
else:
116116
break
117-
else:
118-
assert httpx_response, error
117+
if assertion_error and not httpx_response:
118+
raise assertion_error
119119
return httpx_response
120120

121121
@classmethod
122122
async def async_handler(cls, httpx_request):
123123
httpx_response = None
124-
error = None
124+
assertion_error = None
125125
for router in cls.routers:
126126
try:
127127
httpx_response = await router.async_handler(httpx_request)
128-
except AssertionError as e:
129-
error = e.args[0]
128+
except AllMockedAssertionError as error:
129+
assertion_error = error
130130
continue
131131
else:
132132
break
133-
else:
134-
assert httpx_response, error
133+
if assertion_error and not httpx_response:
134+
raise assertion_error
135135
return httpx_response
136136

137137
@classmethod

respx/models.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,14 @@ def pop(self, name, default=...):
483483
return default
484484

485485

486+
class AllMockedAssertionError(AssertionError):
487+
pass
488+
489+
490+
class AllCalledAssertionError(AssertionError):
491+
pass
492+
493+
486494
class SideEffectError(Exception):
487495
def __init__(self, route: Route, origin: Exception) -> None:
488496
self.route = route

respx/router.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
from .mocks import Mocker
2323
from .models import (
24+
AllCalledAssertionError,
25+
AllMockedAssertionError,
2426
CallList,
2527
PassThrough,
2628
ResolvedRoute,
@@ -97,9 +99,8 @@ def reset(self) -> None:
9799
route.reset()
98100

99101
def assert_all_called(self) -> None:
100-
assert all(
101-
(route.called for route in self.routes)
102-
), "RESPX: some mocked requests were not called!"
102+
if any(not route.called for route in self.routes):
103+
raise AllCalledAssertionError("RESPX: some routes were not called!")
103104

104105
def __getitem__(self, name: str) -> Route:
105106
return self.routes[name]
@@ -238,7 +239,8 @@ def resolver(self, request: httpx.Request) -> Generator[ResolvedRoute, None, Non
238239

239240
if resolved.route is None:
240241
# Assert we always get a route match, if check is enabled
241-
assert not self._assert_all_mocked, f"RESPX: {request!r} not mocked!"
242+
if self._assert_all_mocked:
243+
raise AllMockedAssertionError(f"RESPX: {request!r} not mocked!")
242244

243245
# Auto mock a successful empty response
244246
resolved.response = httpx.Response(200)

tests/test_mock.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@
77
import respx
88
from respx import ASGIHandler, WSGIHandler
99
from respx.mocks import Mocker
10+
from respx.models import AllCalledAssertionError, AllMockedAssertionError
1011
from respx.router import MockRouter
1112

12-
# from respx.transports import MockTransport
13-
1413

1514
@pytest.mark.asyncio
1615
@respx.mock
@@ -112,7 +111,7 @@ def test(respx_mock):
112111
assert respx.calls.call_count == 0
113112
assert respx_mock.calls.call_count == 1
114113

115-
with pytest.raises(AssertionError, match="not mocked"):
114+
with pytest.raises(AllMockedAssertionError):
116115
httpx.post("https://foo.bar/")
117116

118117
assert respx.calls.call_count == 0
@@ -139,7 +138,7 @@ async def test(respx_mock):
139138
assert respx.calls.call_count == 0
140139
assert respx_mock.calls.call_count == 1
141140

142-
with pytest.raises(AssertionError, match="not mocked"):
141+
with pytest.raises(AllMockedAssertionError):
143142
httpx.post("https://foo.bar/")
144143

145144
assert respx.calls.call_count == 0
@@ -415,7 +414,7 @@ async def test_start_stop(client):
415414
@pytest.mark.parametrize(
416415
"assert_all_called,do_post,raises",
417416
[
418-
(True, False, pytest.raises(AssertionError)),
417+
(True, False, pytest.raises(AllCalledAssertionError)),
419418
(True, True, does_not_raise()),
420419
(False, True, does_not_raise()),
421420
(False, False, does_not_raise()),
@@ -438,7 +437,7 @@ async def test_assert_all_called(client, assert_all_called, do_post, raises):
438437
@pytest.mark.asyncio
439438
@pytest.mark.parametrize(
440439
"assert_all_mocked,raises",
441-
[(True, pytest.raises(AssertionError)), (False, does_not_raise())],
440+
[(True, pytest.raises(AllMockedAssertionError)), (False, does_not_raise())],
442441
)
443442
async def test_assert_all_mocked(client, assert_all_mocked, raises):
444443
with raises:
@@ -574,7 +573,7 @@ def test(respx_mock):
574573
client.get("https://pass-through/")
575574
assert pass_route.call_count == 1
576575

577-
with pytest.raises(AssertionError, match="not mocked"):
576+
with pytest.raises(AllMockedAssertionError):
578577
client.get("https://not-mocked/")
579578

580579
with respx.mock(using="httpx"): # extra registered router
@@ -615,7 +614,7 @@ async def content():
615614
await client.get("https://pass-through/")
616615
assert pass_route.call_count == 1
617616

618-
with pytest.raises(AssertionError, match="not mocked"):
617+
with pytest.raises(AllMockedAssertionError):
619618
await client.get("https://not-mocked/")
620619

621620
async with respx.mock(using="httpx"): # extra registered router

tests/test_router.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import pytest
66

77
from respx import Route, Router
8-
from respx.models import PassThrough, RouteList
8+
from respx.models import AllMockedAssertionError, PassThrough, RouteList
99
from respx.patterns import Host, M, Method
1010

1111

@@ -14,10 +14,10 @@ async def test_empty_router():
1414
router = Router()
1515

1616
request = httpx.Request("GET", "https://example.org/")
17-
with pytest.raises(AssertionError, match="not mocked"):
17+
with pytest.raises(AllMockedAssertionError):
1818
router.resolve(request)
1919

20-
with pytest.raises(AssertionError, match="not mocked"):
20+
with pytest.raises(AllMockedAssertionError):
2121
await router.aresolve(request)
2222

2323

tests/test_transports.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import httpx
44
import pytest
55

6-
from respx.models import PassThrough
6+
from respx.models import AllCalledAssertionError, PassThrough
77
from respx.router import Router
88
from respx.transports import MockTransport
99

@@ -59,7 +59,7 @@ async def test_transport_assertions():
5959
transport = MockTransport(router=router)
6060
assert len(w) == 1
6161

62-
with pytest.raises(AssertionError, match="were not called"):
62+
with pytest.raises(AllCalledAssertionError):
6363
async with httpx.AsyncClient(transport=transport) as client:
6464
response = await client.get(url)
6565
assert response.status_code == 404

0 commit comments

Comments
 (0)