Skip to content

Commit af094e6

Browse files
committed
Simulate timeout with enough details in case no mock could be found
1 parent b7d9525 commit af094e6

File tree

3 files changed

+65
-3
lines changed

3 files changed

+65
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
8+
### Changed
9+
- The `httpx.HTTPError` message issued in case no mock could be found is now a `httpx.TimeoutException` containing all information required to fix the test case (if needed).
810

911
## [0.6.0] - 2020-08-07
1012
### Changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,21 @@ def test_exception_raising(httpx_mock: HTTPXMock):
393393

394394
```
395395

396+
Note that default behavior is to send an `httpx.TimeoutException` in case no response can be found. You can then test this kind of exception this way:
397+
398+
```python
399+
import httpx
400+
import pytest
401+
from pytest_httpx import HTTPXMock
402+
403+
404+
def test_timeout(httpx_mock: HTTPXMock):
405+
with httpx.Client() as client:
406+
with pytest.raises(httpx.TimeoutException):
407+
client.get("http://test_url")
408+
409+
```
410+
396411
### How callback is selected
397412

398413
In case more than one callback match request, the first one not yet executed (according to the registration order) will be executed.

pytest_httpx/_httpx_mock.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ def _content_match(self, request: httpx.Request) -> bool:
9494

9595
return request.read() == self.content
9696

97+
def __str__(self) -> str:
98+
matcher_description = f"Match {self.method or 'all'} requests"
99+
if self.url:
100+
matcher_description += f" on {self.url}"
101+
if self.headers:
102+
matcher_description += f" with {self.headers} headers"
103+
if self.content is not None:
104+
matcher_description += f" and {self.content} body"
105+
elif self.content is not None:
106+
matcher_description += f" with {self.content} body"
107+
return matcher_description
108+
97109

98110
class HTTPXMock:
99111
def __init__(self):
@@ -170,10 +182,43 @@ def _handle_request(
170182
if callback:
171183
return callback(request=request, timeout=timeout)
172184

173-
raise httpx.HTTPError(
174-
f"No mock can be found for {request.method} request on {request.url}.",
175-
request=request,
185+
raise httpx.TimeoutException(
186+
self._explain_that_no_response_was_found(request), request=request
187+
)
188+
189+
def _explain_that_no_response_was_found(self, request: httpx.Request) -> str:
190+
expect_headers = set(
191+
[
192+
header
193+
for matcher, _ in self._responses + self._callbacks
194+
if matcher.headers
195+
for header in matcher.headers
196+
]
176197
)
198+
expect_body = any(
199+
[
200+
matcher.content is not None
201+
for matcher, _ in self._responses + self._callbacks
202+
]
203+
)
204+
205+
request_description = f"{request.method} request on {request.url}"
206+
if expect_headers:
207+
request_description += f" with {dict({name: value for name, value in request.headers.items() if name in expect_headers})} headers"
208+
if expect_body:
209+
request_description += f" and {request.read()} body"
210+
elif expect_body:
211+
request_description += f" with {request.read()} body"
212+
213+
matchers_description = "\n".join(
214+
[str(matcher) for matcher, _ in self._responses + self._callbacks]
215+
)
216+
217+
message = f"No response can be found for {request_description}"
218+
if matchers_description:
219+
message += f" amongst:\n{matchers_description}"
220+
221+
return message
177222

178223
def _get_response(self, request: httpx.Request) -> Optional[Response]:
179224
responses = [

0 commit comments

Comments
 (0)