diff --git a/CHANGELOG.md b/CHANGELOG.md index 63139d2..26d8bad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.34.0] - 2024-11-18 +### Added +- `is_optional` parameter is now available on responses and callbacks registration. Allowing to add optional responses while keeping other responses as mandatory. Refer to documentation for more details. +- `is_reusable` parameter is now available on responses and callbacks registration. Allowing to add multi-match responses while keeping other responses as single-match. Refer to documentation for more details. + +### Fixed +- `httpx_mock.get_request` will now also propose to refine filters if more than one request is found instead of only proposing to switch to `httpx_mock.get_requests`. + ## [0.33.0] - 2024-10-28 ### Added - Explicit support for python `3.13`. @@ -396,7 +404,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - First release, should be considered as unstable for now as design might change. -[Unreleased]: https://github.com/Colin-b/pytest_httpx/compare/v0.33.0...HEAD +[Unreleased]: https://github.com/Colin-b/pytest_httpx/compare/v0.34.0...HEAD +[0.34.0]: https://github.com/Colin-b/pytest_httpx/compare/v0.33.0...v0.34.0 [0.33.0]: https://github.com/Colin-b/pytest_httpx/compare/v0.32.0...v0.33.0 [0.32.0]: https://github.com/Colin-b/pytest_httpx/compare/v0.31.2...v0.32.0 [0.31.2]: https://github.com/Colin-b/pytest_httpx/compare/v0.31.1...v0.31.2 diff --git a/README.md b/README.md index 5bc2ca7..007852c 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Build status Coverage Code style: black -Number of tests +Number of tests Number of downloads

@@ -716,9 +716,17 @@ def pytest_collection_modifyitems(session, config, items): By default, `pytest-httpx` will ensure that every response was requested during test execution. -You can use the `httpx_mock` marker `assert_all_responses_were_requested` option to allow fewer requests than what you registered responses for. +If you want to add an optional response, you can use the `is_optional` parameter when [registering a response](#add-responses) or [a callback](#add-callbacks). -This option can be useful if you add responses using shared fixtures. +```python +def test_fewer_requests_than_expected(httpx_mock): + # Even if this response never received a corresponding request, the test will not fail at teardown + httpx_mock.add_response(is_optional=True) +``` + +If you don't have control over the response registration process (shared fixtures), +and you want to allow fewer requests than what you registered responses for, +you can use the `httpx_mock` marker `assert_all_responses_were_requested` option. > [!CAUTION] > Use this option at your own risk of not spotting regression (requests not sent) in your code base! @@ -732,6 +740,18 @@ def test_fewer_requests_than_expected(httpx_mock): httpx_mock.add_response() ``` +Note that the `is_optional` parameter will take precedence over the `assert_all_responses_were_requested` option. +Meaning you can still register a response that will be checked for execution at teardown even if `assert_all_responses_were_requested` was set to `False`. + +```python +import pytest + +@pytest.mark.httpx_mock(assert_all_responses_were_requested=False) +def test_force_expected_request(httpx_mock): + # Even if the assert_all_responses_were_requested option is set, the test will fail at teardown if this is not matched + httpx_mock.add_response(is_optional=False) +``` + #### Allow to not register responses for every request By default, `pytest-httpx` will ensure that every request that was issued was expected. @@ -757,7 +777,22 @@ def test_more_requests_than_expected(httpx_mock): By default, `pytest-httpx` will ensure that every request that was issued was expected. -You can use the `httpx_mock` marker `can_send_already_matched_responses` option to allow multiple requests to match the same registered response. +If you want to add a response once, while allowing it to match more than once, you can use the `is_reusable` parameter when [registering a response](#add-responses) or [a callback](#add-callbacks). + +```python +import httpx + +def test_more_requests_than_responses(httpx_mock): + httpx_mock.add_response(is_reusable=True) + with httpx.Client() as client: + client.get("https://test_url") + # Even if only one response was registered, the test will not fail at teardown as this request will also be matched + client.get("https://test_url") +``` + +If you don't have control over the response registration process (shared fixtures), +and you want to allow multiple requests to match the same registered response, +you can use the `httpx_mock` marker `can_send_already_matched_responses` option. With this option, in case all matching responses have been sent at least once, the last one (according to the registration order) will be sent. diff --git a/pytest_httpx/_httpx_mock.py b/pytest_httpx/_httpx_mock.py index cadec10..e99646e 100644 --- a/pytest_httpx/_httpx_mock.py +++ b/pytest_httpx/_httpx_mock.py @@ -68,7 +68,8 @@ def add_response( :param match_json: JSON decoded HTTP body identifying the request(s) to match. Must be JSON encodable. :param match_data: Multipart data (excluding files) identifying the request(s) to match. Must be a dictionary. :param match_files: Multipart files identifying the request(s) to match. Refer to httpx documentation for more information on supported values: https://www.python-httpx.org/advanced/clients/#multipart-file-encoding - :param match_extensions: Extensions identifying the request(s) to match. Must be a dictionary. + :param is_optional: True will mark this response as optional, False will expect a request matching it. Must be a boolean. Default to the opposite of assert_all_responses_were_requested option value (itself defaulting to True, meaning this parameter default to False). + :param is_reusable: True will allow re-using this response even if it already matched, False prevent re-using it. Must be a boolean. Default to the can_send_already_matched_responses option value (itself defaulting to False). """ json = copy.deepcopy(json) if json is not None else None @@ -111,6 +112,8 @@ def add_callback( :param match_data: Multipart data (excluding files) identifying the request(s) to match. Must be a dictionary. :param match_files: Multipart files identifying the request(s) to match. Refer to httpx documentation for more information on supported values: https://www.python-httpx.org/advanced/clients/#multipart-file-encoding :param match_extensions: Extensions identifying the request(s) to match. Must be a dictionary. + :param is_optional: True will mark this callback as optional, False will expect a request matching it. Must be a boolean. Default to the opposite of assert_all_responses_were_requested option value (itself defaulting to True, meaning this parameter default to False). + :param is_reusable: True will allow re-using this callback even if it already matched, False prevent re-using it. Must be a boolean. Default to the can_send_already_matched_responses option value (itself defaulting to False). """ self._callbacks.append((_RequestMatcher(self._options, **matchers), callback)) @@ -130,6 +133,8 @@ def add_exception(self, exception: Exception, **matchers: Any) -> None: :param match_data: Multipart data (excluding files) identifying the request(s) to match. Must be a dictionary. :param match_files: Multipart files identifying the request(s) to match. Refer to httpx documentation for more information on supported values: https://www.python-httpx.org/advanced/clients/#multipart-file-encoding :param match_extensions: Extensions identifying the request(s) to match. Must be a dictionary. + :param is_optional: True will mark this exception response as optional, False will expect a request matching it. Must be a boolean. Default to the opposite of assert_all_responses_were_requested option value (itself defaulting to True, meaning this parameter default to False). + :param is_reusable: True will allow re-using this exception response even if it already matched, False prevent re-using it. Must be a boolean. Default to the can_send_already_matched_responses option value (itself defaulting to False). """ def exception_callback(request: httpx.Request) -> None: @@ -212,7 +217,7 @@ def _explain_that_no_response_was_found( message += f" amongst:\n{matchers_description}" # If we could not find a response, but we have already matched responses # it might be that user is expecting one of those responses to be reused - if already_matched and not self._options.can_send_already_matched_responses: + if any(not matcher.is_reusable for matcher in already_matched): message += "\n\nIf you wanted to reuse an already matched response instead of registering it again, refer to https://github.com/Colin-b/pytest_httpx/blob/master/README.md#allow-to-register-a-response-for-more-than-one-request" return message @@ -245,7 +250,7 @@ def _get_callback( return callback # Or the last registered (if it can be reused) - if self._options.can_send_already_matched_responses: + if matcher.is_reusable: matcher.nb_calls += 1 return callback @@ -295,7 +300,7 @@ def get_request(self, **matchers: Any) -> Optional[httpx.Request]: requests = self.get_requests(**matchers) assert ( len(requests) <= 1 - ), f"More than one request ({len(requests)}) matched, use get_requests instead." + ), f"More than one request ({len(requests)}) matched, use get_requests instead or refine your filters." return requests[0] if requests else None def reset(self) -> None: @@ -304,20 +309,19 @@ def reset(self) -> None: self._requests_not_matched.clear() def _assert_options(self) -> None: - if self._options.assert_all_responses_were_requested: - callbacks_not_executed = [ - matcher for matcher, _ in self._callbacks if not matcher.nb_calls - ] - matchers_description = "\n".join( - [f"- {matcher}" for matcher in callbacks_not_executed] - ) + callbacks_not_executed = [ + matcher for matcher, _ in self._callbacks if matcher.should_have_matched() + ] + matchers_description = "\n".join( + [f"- {matcher}" for matcher in callbacks_not_executed] + ) - assert not callbacks_not_executed, ( - "The following responses are mocked but not requested:\n" - f"{matchers_description}\n" - "\n" - "If this is on purpose, refer to https://github.com/Colin-b/pytest_httpx/blob/master/README.md#allow-to-register-more-responses-than-what-will-be-requested" - ) + assert not callbacks_not_executed, ( + "The following responses are mocked but not requested:\n" + f"{matchers_description}\n" + "\n" + "If this is on purpose, refer to https://github.com/Colin-b/pytest_httpx/blob/master/README.md#allow-to-register-more-responses-than-what-will-be-requested" + ) if self._options.assert_all_requests_were_expected: requests_description = "\n".join( diff --git a/pytest_httpx/_request_matcher.py b/pytest_httpx/_request_matcher.py index 900ffa9..a2aaa13 100644 --- a/pytest_httpx/_request_matcher.py +++ b/pytest_httpx/_request_matcher.py @@ -39,6 +39,8 @@ def __init__( match_data: Optional[dict[str, Any]] = None, match_files: Optional[Any] = None, match_extensions: Optional[dict[str, Any]] = None, + is_optional: Optional[bool] = None, + is_reusable: Optional[bool] = None, ): self._options = options self.nb_calls = 0 @@ -55,6 +57,8 @@ def __init__( else proxy_url ) self.extensions = match_extensions + self.is_optional = not options.assert_all_responses_were_requested if is_optional is None else is_optional + self.is_reusable = options.can_send_already_matched_responses if is_reusable is None else is_reusable if self._is_matching_body_more_than_one_way(): raise ValueError( "Only one way of matching against the body can be provided. " @@ -177,8 +181,12 @@ def _extensions_match(self, request: httpx.Request) -> bool: for extension_name, extension_value in self.extensions.items() ) + def should_have_matched(self) -> bool: + """Return True if the matcher did not serve its purpose.""" + return not self.is_optional and not self.nb_calls + def __str__(self) -> str: - if self._options.can_send_already_matched_responses: + if self.is_reusable: matcher_description = f"Match {self.method or 'every'} request" else: matcher_description = "Already matched" if self.nb_calls else "Match" diff --git a/pytest_httpx/version.py b/pytest_httpx/version.py index 7811f2a..cedcb43 100644 --- a/pytest_httpx/version.py +++ b/pytest_httpx/version.py @@ -3,4 +3,4 @@ # Major should be incremented in case there is a breaking change. (eg: 2.5.8 -> 3.0.0) # Minor should be incremented in case there is an enhancement. (eg: 2.5.8 -> 2.6.0) # Patch should be incremented in case there is a bug fix. (eg: 2.5.8 -> 2.5.9) -__version__ = "0.33.0" +__version__ = "0.34.0" diff --git a/tests/test_httpx_async.py b/tests/test_httpx_async.py index ee529c6..185ce7f 100644 --- a/tests/test_httpx_async.py +++ b/tests/test_httpx_async.py @@ -48,9 +48,8 @@ async def test_url_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_url_matching_reusing_response(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(url="https://test_url") + httpx_mock.add_response(url="https://test_url", is_reusable=True) async with httpx.AsyncClient() as client: response = await client.get("https://test_url") @@ -61,9 +60,8 @@ async def test_url_matching_reusing_response(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_url_query_string_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(url="https://test_url?a=1&b=2") + httpx_mock.add_response(url="https://test_url?a=1&b=2", is_reusable=True) async with httpx.AsyncClient() as client: response = await client.post("https://test_url?a=1&b=2") @@ -75,11 +73,9 @@ async def test_url_query_string_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(url="https://test_url") + httpx_mock.add_response(url="https://test_url", is_optional=True) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -92,11 +88,9 @@ async def test_url_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_query_string_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(url="https://test_url?a=1&a=2") + httpx_mock.add_response(url="https://test_url?a=1&a=2", is_optional=True) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -110,9 +104,8 @@ async def test_url_query_string_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_method_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(method="get") + httpx_mock.add_response(method="get", is_reusable=True) async with httpx.AsyncClient() as client: response = await client.get("https://test_url") @@ -123,11 +116,9 @@ async def test_method_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_method_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(method="get") + httpx_mock.add_response(method="get", is_optional=True) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -140,9 +131,8 @@ async def test_method_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_reusing_one_response(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(url="https://test_url", content=b"test content") + httpx_mock.add_response(url="https://test_url", content=b"test content", is_reusable=True) async with httpx.AsyncClient() as client: response = await client.get("https://test_url") @@ -171,11 +161,11 @@ async def test_response_with_html_string_body(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_stream_response_streaming(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( url="https://test_url", stream=pytest_httpx.IteratorStream([b"part 1", b"part 2"]), + is_reusable=True, ) async with httpx.AsyncClient() as client: @@ -201,11 +191,11 @@ async def test_stream_response_streaming(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_content_response_streaming(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( url="https://test_url", content=b"part 1 and 2", + is_reusable=True, ) async with httpx.AsyncClient() as client: @@ -229,11 +219,11 @@ async def test_content_response_streaming(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_text_response_streaming(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( url="https://test_url", text="part 1 and 2", + is_reusable=True, ) async with httpx.AsyncClient() as client: @@ -257,9 +247,8 @@ async def test_text_response_streaming(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_default_response_streaming(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient() as client: async with client.stream(method="GET", url="https://test_url") as response: @@ -295,10 +284,9 @@ async def test_with_many_responses(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_with_many_reused_responses(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response(url="https://test_url", content=b"test content 1") - httpx_mock.add_response(url="https://test_url", content=b"test content 2") + httpx_mock.add_response(url="https://test_url", content=b"test content 2", is_reusable=True) async with httpx.AsyncClient() as client: response = await client.get("https://test_url") @@ -730,9 +718,8 @@ async def test_requests_retrieval(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_requests_retrieval_on_same_url(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(url="https://test_url") + httpx_mock.add_response(url="https://test_url", is_reusable=True) async with httpx.AsyncClient() as client: await client.get("https://test_url", headers={"X-TEST": "test header 1"}) @@ -745,9 +732,8 @@ async def test_requests_retrieval_on_same_url(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_request_retrieval_on_same_url(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient() as client: await client.get("https://test_url", headers={"X-TEST": "test header 1"}) @@ -758,9 +744,8 @@ async def test_request_retrieval_on_same_url(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_requests_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient() as client: await client.get("https://test_url", headers={"X-TEST": "test header 1"}) @@ -773,9 +758,8 @@ async def test_requests_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_request_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient() as client: await client.get("https://test_url", headers={"X-TEST": "test header 1"}) @@ -786,9 +770,8 @@ async def test_request_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_requests_retrieval_on_same_url_and_method(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient() as client: await client.get("https://test_url", headers={"X-TEST": "test header 1"}) @@ -803,9 +786,8 @@ async def test_requests_retrieval_on_same_url_and_method(httpx_mock: HTTPXMock) @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_default_requests_retrieval(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient() as client: await client.post("https://test_url", headers={"X-TEST": "test header 1"}) @@ -938,12 +920,11 @@ async def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_callback_executed_twice(httpx_mock: HTTPXMock) -> None: def custom_response(request: httpx.Request) -> httpx.Response: return httpx.Response(status_code=200, json=["content"]) - httpx_mock.add_callback(custom_response) + httpx_mock.add_callback(custom_response, is_reusable=True) async with httpx.AsyncClient() as client: response = await client.get("https://test_url") @@ -956,12 +937,11 @@ def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_async_callback_executed_twice(httpx_mock: HTTPXMock) -> None: async def custom_response(request: httpx.Request) -> httpx.Response: return httpx.Response(status_code=200, json=["content"]) - httpx_mock.add_callback(custom_response) + httpx_mock.add_callback(custom_response, is_reusable=True) async with httpx.AsyncClient() as client: response = await client.get("https://test_url") @@ -974,13 +954,12 @@ async def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_callback_registered_after_response(httpx_mock: HTTPXMock) -> None: def custom_response(request: httpx.Request) -> httpx.Response: return httpx.Response(status_code=200, json=["content2"]) httpx_mock.add_response(json=["content1"]) - httpx_mock.add_callback(custom_response) + httpx_mock.add_callback(custom_response, is_reusable=True) async with httpx.AsyncClient() as client: response = await client.get("https://test_url") @@ -998,13 +977,12 @@ def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_async_callback_registered_after_response(httpx_mock: HTTPXMock) -> None: async def custom_response(request: httpx.Request) -> httpx.Response: return httpx.Response(status_code=200, json=["content2"]) httpx_mock.add_response(json=["content1"]) - httpx_mock.add_callback(custom_response) + httpx_mock.add_callback(custom_response, is_reusable=True) async with httpx.AsyncClient() as client: response = await client.get("https://test_url") @@ -1022,13 +1000,12 @@ async def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_response_registered_after_callback(httpx_mock: HTTPXMock) -> None: def custom_response(request: httpx.Request) -> httpx.Response: return httpx.Response(status_code=200, json=["content1"]) httpx_mock.add_callback(custom_response) - httpx_mock.add_response(json=["content2"]) + httpx_mock.add_response(json=["content2"], is_reusable=True) async with httpx.AsyncClient() as client: response = await client.get("https://test_url") @@ -1046,13 +1023,12 @@ def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_response_registered_after_async_callback(httpx_mock: HTTPXMock) -> None: async def custom_response(request: httpx.Request) -> httpx.Response: return httpx.Response(status_code=200, json=["content1"]) httpx_mock.add_callback(custom_response) - httpx_mock.add_response(json=["content2"]) + httpx_mock.add_response(json=["content2"], is_reusable=True) async with httpx.AsyncClient() as client: response = await client.get("https://test_url") @@ -1070,12 +1046,11 @@ async def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_callback_matching_method(httpx_mock: HTTPXMock) -> None: def custom_response(request: httpx.Request) -> httpx.Response: return httpx.Response(status_code=200, json=["content"]) - httpx_mock.add_callback(custom_response, method="GET") + httpx_mock.add_callback(custom_response, method="GET", is_reusable=True) async with httpx.AsyncClient() as client: response = await client.get("https://test_url") @@ -1088,12 +1063,11 @@ def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_async_callback_matching_method(httpx_mock: HTTPXMock) -> None: async def custom_response(request: httpx.Request) -> httpx.Response: return httpx.Response(status_code=200, json=["content"]) - httpx_mock.add_callback(custom_response, method="GET") + httpx_mock.add_callback(custom_response, method="GET", is_reusable=True) async with httpx.AsyncClient() as client: response = await client.get("https://test_url") @@ -1111,14 +1085,13 @@ def test_request_retrieval_with_more_than_one(testdir: Testdir) -> None: """ testdir.makepyfile( """ - import pytest import httpx + import pytest @pytest.mark.asyncio - @pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_request_retrieval_with_more_than_one(httpx_mock): - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient() as client: await client.get("https://test_url", headers={"X-TEST": "test header 1"}) @@ -1131,7 +1104,7 @@ async def test_request_retrieval_with_more_than_one(httpx_mock): result.assert_outcomes(failed=1) result.stdout.fnmatch_lines( [ - "*AssertionError: More than one request (2) matched, use get_requests instead." + "*AssertionError: More than one request (2) matched, use get_requests instead or refine your filters." ] ) @@ -1160,13 +1133,11 @@ async def test_multi_value_headers_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_multi_value_headers_not_matching_single_value_issued( httpx_mock: HTTPXMock, ) -> None: - httpx_mock.add_response(match_headers={"my-custom-header": "value1"}) + httpx_mock.add_response(match_headers={"my-custom-header": "value1"}, is_optional=True) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1185,13 +1156,11 @@ async def test_multi_value_headers_not_matching_single_value_issued( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_multi_value_headers_not_matching_multi_value_issued( httpx_mock: HTTPXMock, ) -> None: - httpx_mock.add_response(match_headers={"my-custom-header": "value1, value2"}) + httpx_mock.add_response(match_headers={"my-custom-header": "value1, value2"}, is_optional=True) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1210,12 +1179,10 @@ async def test_multi_value_headers_not_matching_multi_value_issued( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_headers_matching_respect_case(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( - match_headers={"user-agent": f"python-httpx/{httpx.__version__}"} + match_headers={"user-agent": f"python-httpx/{httpx.__version__}"}, is_optional=True ) async with httpx.AsyncClient() as client: @@ -1229,16 +1196,15 @@ async def test_headers_matching_respect_case(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_headers_not_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( match_headers={ "User-Agent": f"python-httpx/{httpx.__version__}", "Host": "test_url2", "Host2": "test_url", - } + }, + is_optional=True ) async with httpx.AsyncClient() as client: @@ -1252,9 +1218,7 @@ async def test_headers_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_not_matching_upper_case_headers_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1262,6 +1226,7 @@ async def test_url_not_matching_upper_case_headers_matching( method="GET", url="https://test_url?q=b", match_headers={"MyHeader": "Something"}, + is_optional=True, ) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1292,11 +1257,9 @@ async def test_proxy_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_proxy_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(proxy_url="http://my_test_proxy") + httpx_mock.add_response(proxy_url="http://my_test_proxy", is_optional=True) async with httpx.AsyncClient(proxy="http://my_test_proxy") as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1309,11 +1272,9 @@ async def test_proxy_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_proxy_not_existing(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(proxy_url="http://my_test_proxy") + httpx_mock.add_response(proxy_url="http://my_test_proxy", is_optional=True) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1326,9 +1287,8 @@ async def test_proxy_not_existing(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_requests_retrieval_content_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient() as client: await client.post("https://test_url", content=b"This is the body") @@ -1339,9 +1299,8 @@ async def test_requests_retrieval_content_matching(httpx_mock: HTTPXMock) -> Non @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_requests_retrieval_json_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient() as client: await client.post("https://test_url", json=["my_str"]) @@ -1352,9 +1311,8 @@ async def test_requests_retrieval_json_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_requests_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient( mounts={ @@ -1374,9 +1332,8 @@ async def test_requests_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_request_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient( mounts={ @@ -1394,11 +1351,10 @@ async def test_request_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_requests_retrieval_files_and_data_matching( httpx_mock: HTTPXMock, ) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient() as client: await client.put( @@ -1429,9 +1385,8 @@ async def test_requests_retrieval_files_and_data_matching( @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_request_retrieval_files_and_data_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient() as client: await client.put( @@ -1449,9 +1404,8 @@ async def test_request_retrieval_files_and_data_matching(httpx_mock: HTTPXMock) @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_requests_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient() as client: await client.get("https://test_url") @@ -1470,9 +1424,8 @@ async def test_requests_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_request_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) async with httpx.AsyncClient() as client: await client.get("https://test_url", timeout=httpx.Timeout(5, read=10)) @@ -1485,11 +1438,9 @@ async def test_request_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> N @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_content_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(match_content=b"This is the body") + httpx_mock.add_response(match_content=b"This is the body", is_optional=True) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1520,11 +1471,9 @@ async def test_json_partial_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_json_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(match_json={"a": 1, "b": 2}) + httpx_mock.add_response(match_json={"a": 1, "b": 2}, is_optional=True) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1537,13 +1486,12 @@ async def test_json_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_headers_and_json_not_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( match_json={"a": 1, "b": 2}, - match_headers={"foo": "bar"}, + match_headers={"foo": "bar"}, + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1557,11 +1505,9 @@ async def test_headers_and_json_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_match_json_invalid_json(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(match_json={"a": 1, "b": 2}) + httpx_mock.add_response(match_json={"a": 1, "b": 2}, is_optional=True) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1586,16 +1532,15 @@ async def test_headers_and_content_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_headers_not_matching_and_content_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( match_headers={ "User-Agent": f"python-httpx/{httpx.__version__}", "Host": "test_url2", }, - match_content=b"This is the body", + match_content=b"This is the body", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1609,16 +1554,15 @@ async def test_headers_not_matching_and_content_matching(httpx_mock: HTTPXMock) @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_headers_matching_and_content_not_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( match_headers={ "User-Agent": f"python-httpx/{httpx.__version__}", "Host": "test_url", }, - match_content=b"This is the body2", + match_content=b"This is the body2", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1632,16 +1576,15 @@ async def test_headers_matching_and_content_not_matching(httpx_mock: HTTPXMock) @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_headers_and_content_not_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( match_headers={ "User-Agent": f"python-httpx/{httpx.__version__}", "Host": "test_url2", }, - match_content=b"This is the body2", + match_content=b"This is the body2", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1668,9 +1611,7 @@ async def test_url_and_headers_and_content_matching(httpx_mock: HTTPXMock) -> No @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_headers_not_matching_and_url_and_content_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1680,7 +1621,8 @@ async def test_headers_not_matching_and_url_and_content_matching( "User-Agent": f"python-httpx/{httpx.__version__}", "Host": "test_url2", }, - match_content=b"This is the body", + match_content=b"This is the body", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1694,9 +1636,7 @@ async def test_headers_not_matching_and_url_and_content_matching( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_and_headers_not_matching_and_content_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1706,7 +1646,8 @@ async def test_url_and_headers_not_matching_and_content_matching( "User-Agent": f"python-httpx/{httpx.__version__}", "Host": "test_url2", }, - match_content=b"This is the body", + match_content=b"This is the body", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1720,9 +1661,7 @@ async def test_url_and_headers_not_matching_and_content_matching( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_and_headers_matching_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1732,7 +1671,8 @@ async def test_url_and_headers_matching_and_content_not_matching( "User-Agent": f"python-httpx/{httpx.__version__}", "Host": "test_url", }, - match_content=b"This is the body2", + match_content=b"This is the body2", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1746,9 +1686,7 @@ async def test_url_and_headers_matching_and_content_not_matching( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_headers_matching_and_url_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1758,7 +1696,8 @@ async def test_headers_matching_and_url_and_content_not_matching( "User-Agent": f"python-httpx/{httpx.__version__}", "Host": "test_url", }, - match_content=b"This is the body2", + match_content=b"This is the body2", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1772,9 +1711,7 @@ async def test_headers_matching_and_url_and_content_not_matching( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_matching_and_headers_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1784,7 +1721,8 @@ async def test_url_matching_and_headers_and_content_not_matching( "User-Agent": f"python-httpx/{httpx.__version__}", "Host": "test_url2", }, - match_content=b"This is the body2", + match_content=b"This is the body2", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1798,9 +1736,7 @@ async def test_url_matching_and_headers_and_content_not_matching( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_and_headers_and_content_not_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( url="https://test_url2", @@ -1809,6 +1745,7 @@ async def test_url_and_headers_and_content_not_matching(httpx_mock: HTTPXMock) - "Host": "test_url2", }, match_content=b"This is the body2", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1838,9 +1775,7 @@ async def test_method_and_url_and_headers_and_content_matching( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_headers_not_matching_and_method_and_url_and_content_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1852,6 +1787,7 @@ async def test_headers_not_matching_and_method_and_url_and_content_matching( "Host": "test_url2", }, match_content=b"This is the body", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1865,9 +1801,7 @@ async def test_headers_not_matching_and_method_and_url_and_content_matching( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_and_headers_not_matching_and_method_and_content_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1879,6 +1813,7 @@ async def test_url_and_headers_not_matching_and_method_and_content_matching( "Host": "test_url2", }, match_content=b"This is the body", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1892,9 +1827,7 @@ async def test_url_and_headers_not_matching_and_method_and_content_matching( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_method_and_url_and_headers_matching_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1906,6 +1839,7 @@ async def test_method_and_url_and_headers_matching_and_content_not_matching( "Host": "test_url", }, match_content=b"This is the body2", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1919,9 +1853,7 @@ async def test_method_and_url_and_headers_matching_and_content_not_matching( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_method_and_headers_matching_and_url_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1933,6 +1865,7 @@ async def test_method_and_headers_matching_and_url_and_content_not_matching( "Host": "test_url", }, match_content=b"This is the body2", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1946,9 +1879,7 @@ async def test_method_and_headers_matching_and_url_and_content_not_matching( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_method_and_url_matching_and_headers_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1960,6 +1891,7 @@ async def test_method_and_url_matching_and_headers_and_content_not_matching( "Host": "test_url2", }, match_content=b"This is the body2", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -1973,9 +1905,7 @@ async def test_method_and_url_matching_and_headers_and_content_not_matching( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_method_matching_and_url_and_headers_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1987,6 +1917,7 @@ async def test_method_matching_and_url_and_headers_and_content_not_matching( "Host": "test_url2", }, match_content=b"This is the body2", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -2000,9 +1931,7 @@ async def test_method_matching_and_url_and_headers_and_content_not_matching( @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_method_and_url_and_headers_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -2014,6 +1943,7 @@ async def test_method_and_url_and_headers_and_content_not_matching( "Host": "test_url2", }, match_content=b"This is the body2", + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -2149,11 +2079,10 @@ async def test_mutating_json(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) async def test_streams_are_not_cascading_resulting_in_maximum_recursion( httpx_mock: HTTPXMock, ) -> None: - httpx_mock.add_response(json={"abc": "def"}) + httpx_mock.add_response(json={"abc": "def"}, is_reusable=True) async with httpx.AsyncClient() as client: tasks = [client.get("https://test_url") for _ in range(950)] await asyncio.gather(*tasks) @@ -2269,9 +2198,7 @@ async def test_files_and_data_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_files_not_matching_name(httpx_mock: HTTPXMock, monkeypatch) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( @@ -2280,7 +2207,7 @@ async def test_files_not_matching_name(httpx_mock: HTTPXMock, monkeypatch) -> No lambda length: b"\xfb\xe4\x95\xef\xe4\xcdA\xb9A\xca\x13\xe2T\xd6\xb0\x18", ) - httpx_mock.add_response(match_files={"name2": ("file_name", b"File content")}) + httpx_mock.add_response(match_files={"name2": ("file_name", b"File content")}, is_optional=True) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -2295,9 +2222,7 @@ async def test_files_not_matching_name(httpx_mock: HTTPXMock, monkeypatch) -> No @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_files_not_matching_file_name(httpx_mock: HTTPXMock, monkeypatch) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( @@ -2306,7 +2231,7 @@ async def test_files_not_matching_file_name(httpx_mock: HTTPXMock, monkeypatch) lambda length: b"\xfb\xe4\x95\xef\xe4\xcdA\xb9A\xca\x13\xe2T\xd6\xb0\x18", ) - httpx_mock.add_response(match_files={"name": ("file_name2", b"File content")}) + httpx_mock.add_response(match_files={"name": ("file_name2", b"File content")}, is_optional=True) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -2321,9 +2246,7 @@ async def test_files_not_matching_file_name(httpx_mock: HTTPXMock, monkeypatch) @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_files_not_matching_content(httpx_mock: HTTPXMock, monkeypatch) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( @@ -2332,7 +2255,7 @@ async def test_files_not_matching_content(httpx_mock: HTTPXMock, monkeypatch) -> lambda length: b"\xfb\xe4\x95\xef\xe4\xcdA\xb9A\xca\x13\xe2T\xd6\xb0\x18", ) - httpx_mock.add_response(match_files={"name": ("file_name", b"File content2")}) + httpx_mock.add_response(match_files={"name": ("file_name", b"File content2")}, is_optional=True) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -2347,9 +2270,7 @@ async def test_files_not_matching_content(httpx_mock: HTTPXMock, monkeypatch) -> @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_files_matching_but_data_not_matching( httpx_mock: HTTPXMock, monkeypatch ) -> None: @@ -2363,6 +2284,7 @@ async def test_files_matching_but_data_not_matching( httpx_mock.add_response( match_files={"name": ("file_name", b"File content")}, match_data={"field": "value"}, + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -2391,12 +2313,11 @@ async def test_timeout_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_timeout_not_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( - match_extensions={"timeout": {"connect": 5, "read": 5, "write": 10, "pool": 5}} + match_extensions={"timeout": {"connect": 5, "read": 5, "write": 10, "pool": 5}}, + is_optional=True, ) async with httpx.AsyncClient() as client: @@ -2421,11 +2342,9 @@ async def test_extensions_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_extensions_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(match_extensions={"test": "value"}) + httpx_mock.add_response(match_extensions={"test": "value"}, is_optional=True) async with httpx.AsyncClient() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -2435,3 +2354,60 @@ async def test_extensions_not_matching(httpx_mock: HTTPXMock) -> None: == """No response can be found for GET request on https://test_url with {'test': 'value2'} extensions amongst: - Match any request with {'test': 'value'} extensions""" ) + + +@pytest.mark.asyncio +async def test_optional_response_not_matched(httpx_mock: HTTPXMock) -> None: + # This response is optional and the fact that it was never requested should not trigger anything + httpx_mock.add_response(url="https://test_url", is_optional=True) + httpx_mock.add_response(url="https://test_url2") + + async with httpx.AsyncClient() as client: + response = await client.get("https://test_url2") + assert response.content == b"" + + +@pytest.mark.asyncio +async def test_optional_response_matched(httpx_mock: HTTPXMock) -> None: + # This response is optional and the fact that it was never requested should not trigger anything + httpx_mock.add_response(url="https://test_url", is_optional=True) + httpx_mock.add_response(url="https://test_url2") + + async with httpx.AsyncClient() as client: + response1 = await client.get("https://test_url") + response2 = await client.get("https://test_url2") + assert response1.content == b"" + assert response2.content == b"" + + +@pytest.mark.asyncio +@pytest.mark.httpx_mock(assert_all_responses_were_requested=False) +async def test_mandatory_response_matched(httpx_mock: HTTPXMock) -> None: + # This response is optional and the fact that it was never requested should not trigger anything + httpx_mock.add_response(url="https://test_url") + # This response MUST be requested (overrides global settings via marker) + httpx_mock.add_response(url="https://test_url2", is_optional=False) + + async with httpx.AsyncClient() as client: + response = await client.get("https://test_url2") + assert response.content == b"" + + +@pytest.mark.asyncio +async def test_multi_response_matched_once(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response(url="https://test_url", is_reusable=True) + + async with httpx.AsyncClient() as client: + response = await client.get("https://test_url") + assert response.content == b"" + + +@pytest.mark.asyncio +async def test_multi_response_matched_twice(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response(url="https://test_url", is_reusable=True) + + async with httpx.AsyncClient() as client: + response1 = await client.get("https://test_url") + response2 = await client.get("https://test_url") + assert response1.content == b"" + assert response2.content == b"" diff --git a/tests/test_httpx_sync.py b/tests/test_httpx_sync.py index 802d2fa..c71d918 100644 --- a/tests/test_httpx_sync.py +++ b/tests/test_httpx_sync.py @@ -41,9 +41,8 @@ def test_url_matching(httpx_mock: HTTPXMock) -> None: assert response.content == b"" -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_url_matching_reusing_response(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(url="https://test_url") + httpx_mock.add_response(url="https://test_url", is_reusable=True) with httpx.Client() as client: response = client.get("https://test_url") @@ -53,9 +52,8 @@ def test_url_matching_reusing_response(httpx_mock: HTTPXMock) -> None: assert response.content == b"" -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_url_query_string_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(url="https://test_url?a=1&b=2") + httpx_mock.add_response(url="https://test_url?a=1&b=2", is_reusable=True) with httpx.Client() as client: response = client.post("https://test_url?a=1&b=2") @@ -66,11 +64,9 @@ def test_url_query_string_matching(httpx_mock: HTTPXMock) -> None: assert response.content == b"" -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(url="https://test_url") + httpx_mock.add_response(url="https://test_url", is_optional=True) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -82,11 +78,9 @@ def test_url_not_matching(httpx_mock: HTTPXMock) -> None: ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_query_string_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(url="https://test_url?a=1&a=2") + httpx_mock.add_response(url="https://test_url?a=1&a=2", is_optional=True) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -99,9 +93,8 @@ def test_url_query_string_not_matching(httpx_mock: HTTPXMock) -> None: ) -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_method_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(method="get") + httpx_mock.add_response(method="get", is_reusable=True) with httpx.Client() as client: response = client.get("https://test_url") @@ -111,11 +104,9 @@ def test_method_matching(httpx_mock: HTTPXMock) -> None: assert response.content == b"" -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_method_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(method="get") + httpx_mock.add_response(method="get", is_optional=True) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -127,9 +118,8 @@ def test_method_not_matching(httpx_mock: HTTPXMock) -> None: ) -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_reusing_one_response(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(url="https://test_url", content=b"test content") + httpx_mock.add_response(url="https://test_url", content=b"test content", is_reusable=True) with httpx.Client() as client: response = client.get("https://test_url") @@ -155,14 +145,13 @@ def test_response_with_html_string_body(httpx_mock: HTTPXMock) -> None: assert response.text == "test content" -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_not_matching_upper_case_headers_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( method="GET", url="https://test_url?q=b", match_headers={"MyHeader": "Something"}, + is_optional=True, ) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -174,11 +163,11 @@ def test_url_not_matching_upper_case_headers_matching(httpx_mock: HTTPXMock) -> ) -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_stream_response_streaming(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( url="https://test_url", stream=pytest_httpx.IteratorStream([b"part 1", b"part 2"]), + is_reusable=True, ) with httpx.Client() as client: @@ -196,11 +185,11 @@ def test_stream_response_streaming(httpx_mock: HTTPXMock) -> None: list(response.iter_raw()) -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_content_response_streaming(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( url="https://test_url", content=b"part 1 and 2", + is_reusable=True, ) with httpx.Client() as client: @@ -218,11 +207,11 @@ def test_content_response_streaming(httpx_mock: HTTPXMock) -> None: list(response.iter_raw()) -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_text_response_streaming(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( url="https://test_url", text="part 1 and 2", + is_reusable=True, ) with httpx.Client() as client: @@ -240,9 +229,8 @@ def test_text_response_streaming(httpx_mock: HTTPXMock) -> None: list(response.iter_raw()) -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_default_response_streaming(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client() as client: with client.stream(method="GET", url="https://test_url") as response: @@ -275,10 +263,9 @@ def test_with_many_responses(httpx_mock: HTTPXMock) -> None: assert response.content == b"test content 2" -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_with_many_reused_responses(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response(url="https://test_url", content=b"test content 1") - httpx_mock.add_response(url="https://test_url", content=b"test content 2") + httpx_mock.add_response(url="https://test_url", content=b"test content 2", is_reusable=True) with httpx.Client() as client: response = client.get("https://test_url") @@ -630,9 +617,8 @@ def test_requests_retrieval(httpx_mock: HTTPXMock) -> None: ) -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_requests_retrieval_on_same_url(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(url="https://test_url") + httpx_mock.add_response(url="https://test_url", is_reusable=True) with httpx.Client() as client: client.get("https://test_url", headers={"X-TEST": "test header 1"}) @@ -644,9 +630,8 @@ def test_requests_retrieval_on_same_url(httpx_mock: HTTPXMock) -> None: assert requests[1].headers["x-test"] == "test header 2" -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_request_retrieval_on_same_url(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client() as client: client.get("https://test_url", headers={"X-TEST": "test header 1"}) @@ -656,9 +641,8 @@ def test_request_retrieval_on_same_url(httpx_mock: HTTPXMock) -> None: assert request.headers["x-test"] == "test header 1" -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_requests_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client() as client: client.get("https://test_url", headers={"X-TEST": "test header 1"}) @@ -670,9 +654,8 @@ def test_requests_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: assert requests[1].headers["x-test"] == "test header 2" -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_request_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client() as client: client.get("https://test_url", headers={"X-TEST": "test header 1"}) @@ -682,9 +665,8 @@ def test_request_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: assert request.headers["x-test"] == "test header 1" -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_requests_retrieval_on_same_url_and_method(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client() as client: client.get("https://test_url", headers={"X-TEST": "test header 1"}) @@ -698,9 +680,8 @@ def test_requests_retrieval_on_same_url_and_method(httpx_mock: HTTPXMock) -> Non assert requests[1].headers["x-test"] == "test header 2" -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_default_requests_retrieval(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client() as client: client.post("https://test_url", headers={"X-TEST": "test header 1"}) @@ -797,12 +778,11 @@ def custom_response(request: httpx.Request) -> httpx.Response: assert response.headers["content-type"] == "application/json" -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_callback_executed_twice(httpx_mock: HTTPXMock) -> None: def custom_response(request: httpx.Request) -> httpx.Response: return httpx.Response(status_code=200, json=["content"]) - httpx_mock.add_callback(custom_response) + httpx_mock.add_callback(custom_response, is_reusable=True) with httpx.Client() as client: response = client.get("https://test_url") @@ -814,13 +794,12 @@ def custom_response(request: httpx.Request) -> httpx.Response: assert response.headers["content-type"] == "application/json" -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_callback_registered_after_response(httpx_mock: HTTPXMock) -> None: def custom_response(request: httpx.Request) -> httpx.Response: return httpx.Response(status_code=200, json=["content2"]) httpx_mock.add_response(json=["content1"]) - httpx_mock.add_callback(custom_response) + httpx_mock.add_callback(custom_response, is_reusable=True) with httpx.Client() as client: response = client.get("https://test_url") @@ -837,13 +816,12 @@ def custom_response(request: httpx.Request) -> httpx.Response: assert response.headers["content-type"] == "application/json" -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_response_registered_after_callback(httpx_mock: HTTPXMock) -> None: def custom_response(request: httpx.Request) -> httpx.Response: return httpx.Response(status_code=200, json=["content1"]) httpx_mock.add_callback(custom_response) - httpx_mock.add_response(json=["content2"]) + httpx_mock.add_response(json=["content2"], is_reusable=True) with httpx.Client() as client: response = client.get("https://test_url") @@ -860,12 +838,11 @@ def custom_response(request: httpx.Request) -> httpx.Response: assert response.headers["content-type"] == "application/json" -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_callback_matching_method(httpx_mock: HTTPXMock) -> None: def custom_response(request: httpx.Request) -> httpx.Response: return httpx.Response(status_code=200, json=["content"]) - httpx_mock.add_callback(custom_response, method="GET") + httpx_mock.add_callback(custom_response, method="GET", is_reusable=True) with httpx.Client() as client: response = client.get("https://test_url") @@ -884,12 +861,10 @@ def test_request_retrieval_with_more_than_one(testdir: Testdir) -> None: testdir.makepyfile( """ import httpx - import pytest - @pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_request_retrieval_with_more_than_one(httpx_mock): - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client() as client: client.get("https://test_url", headers={"X-TEST": "test header 1"}) @@ -902,7 +877,7 @@ def test_request_retrieval_with_more_than_one(httpx_mock): result.assert_outcomes(failed=1) result.stdout.fnmatch_lines( [ - "*AssertionError: More than one request (2) matched, use get_requests instead." + "*AssertionError: More than one request (2) matched, use get_requests instead or refine your filters." ] ) @@ -928,13 +903,11 @@ def test_multi_value_headers_matching(httpx_mock: HTTPXMock) -> None: assert response.content == b"" -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_multi_value_headers_not_matching_single_value_issued( httpx_mock: HTTPXMock, ) -> None: - httpx_mock.add_response(match_headers={"my-custom-header": "value1"}) + httpx_mock.add_response(match_headers={"my-custom-header": "value1"}, is_optional=True) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -952,13 +925,11 @@ def test_multi_value_headers_not_matching_single_value_issued( ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_multi_value_headers_not_matching_multi_value_issued( httpx_mock: HTTPXMock, ) -> None: - httpx_mock.add_response(match_headers={"my-custom-header": "value1, value2"}) + httpx_mock.add_response(match_headers={"my-custom-header": "value1, value2"}, is_optional=True) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -976,12 +947,10 @@ def test_multi_value_headers_not_matching_multi_value_issued( ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_headers_matching_respect_case(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( - match_headers={"user-agent": f"python-httpx/{httpx.__version__}"} + match_headers={"user-agent": f"python-httpx/{httpx.__version__}"}, is_optional=True ) with httpx.Client() as client: @@ -994,16 +963,15 @@ def test_headers_matching_respect_case(httpx_mock: HTTPXMock) -> None: ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_headers_not_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( match_headers={ "User-Agent": f"python-httpx/{httpx.__version__}", "Host": "test_url2", "Host2": "test_url", - } + }, + is_optional=True ) with httpx.Client() as client: @@ -1032,11 +1000,9 @@ def test_proxy_matching(httpx_mock: HTTPXMock) -> None: assert response.read() == b"" -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_proxy_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(proxy_url="http://my_test_proxy") + httpx_mock.add_response(proxy_url="http://my_test_proxy", is_optional=True) with httpx.Client(proxy="http://my_test_proxy") as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1048,11 +1014,9 @@ def test_proxy_not_matching(httpx_mock: HTTPXMock) -> None: ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_proxy_not_existing(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(proxy_url="http://my_test_proxy") + httpx_mock.add_response(proxy_url="http://my_test_proxy", is_optional=True) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1064,9 +1028,8 @@ def test_proxy_not_existing(httpx_mock: HTTPXMock) -> None: ) -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_requests_retrieval_content_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client() as client: client.post("https://test_url", content=b"This is the body") @@ -1076,9 +1039,8 @@ def test_requests_retrieval_content_matching(httpx_mock: HTTPXMock) -> None: assert len(httpx_mock.get_requests(match_content=b"This is the body")) == 2 -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_requests_retrieval_json_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client() as client: client.post("https://test_url", json=["my_str"]) @@ -1088,9 +1050,8 @@ def test_requests_retrieval_json_matching(httpx_mock: HTTPXMock) -> None: assert len(httpx_mock.get_requests(match_json=["my_str"])) == 2 -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_requests_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client( mounts={ @@ -1107,9 +1068,8 @@ def test_requests_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: ) -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_request_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client( mounts={ @@ -1124,9 +1084,8 @@ def test_request_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: assert httpx_mock.get_request(proxy_url="http://my_test_proxy/") -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_requests_retrieval_files_and_data_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client() as client: client.put( @@ -1156,9 +1115,8 @@ def test_requests_retrieval_files_and_data_matching(httpx_mock: HTTPXMock) -> No ) -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_request_retrieval_files_and_data_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client() as client: client.put( @@ -1175,9 +1133,8 @@ def test_request_retrieval_files_and_data_matching(httpx_mock: HTTPXMock) -> Non ) -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_requests_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client() as client: client.get("https://test_url") @@ -1195,9 +1152,8 @@ def test_requests_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> None: ) -@pytest.mark.httpx_mock(can_send_already_matched_responses=True) def test_request_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response() + httpx_mock.add_response(is_reusable=True) with httpx.Client() as client: client.get("https://test_url", timeout=httpx.Timeout(5, read=10)) @@ -1209,11 +1165,9 @@ def test_request_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> None: ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_content_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(match_content=b"This is the body") + httpx_mock.add_response(match_content=b"This is the body", is_optional=True) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1276,11 +1230,9 @@ def test_json_partial_matching(httpx_mock: HTTPXMock) -> None: assert response.read() == b"" -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_json_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(match_json={"a": 1, "b": 2}) + httpx_mock.add_response(match_json={"a": 1, "b": 2}, is_optional=True) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1292,13 +1244,12 @@ def test_json_not_matching(httpx_mock: HTTPXMock) -> None: ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_headers_and_json_not_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( match_json={"a": 1, "b": 2}, match_headers={"foo": "bar"}, + is_optional=True, ) with httpx.Client() as client: @@ -1311,11 +1262,9 @@ def test_headers_and_json_not_matching(httpx_mock: HTTPXMock) -> None: ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_match_json_invalid_json(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(match_json={"a": 1, "b": 2}) + httpx_mock.add_response(match_json={"a": 1, "b": 2}, is_optional=True) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1338,9 +1287,7 @@ def test_headers_and_content_matching(httpx_mock: HTTPXMock) -> None: assert response.content == b"" -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_headers_not_matching_and_content_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( match_headers={ @@ -1348,6 +1295,7 @@ def test_headers_not_matching_and_content_matching(httpx_mock: HTTPXMock) -> Non "Host": "test_url2", }, match_content=b"This is the body", + is_optional=True, ) with httpx.Client() as client: @@ -1360,9 +1308,7 @@ def test_headers_not_matching_and_content_matching(httpx_mock: HTTPXMock) -> Non ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_headers_matching_and_content_not_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( match_headers={ @@ -1370,6 +1316,7 @@ def test_headers_matching_and_content_not_matching(httpx_mock: HTTPXMock) -> Non "Host": "test_url", }, match_content=b"This is the body2", + is_optional=True, ) with httpx.Client() as client: @@ -1382,9 +1329,7 @@ def test_headers_matching_and_content_not_matching(httpx_mock: HTTPXMock) -> Non ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_headers_and_content_not_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( match_headers={ @@ -1392,6 +1337,7 @@ def test_headers_and_content_not_matching(httpx_mock: HTTPXMock) -> None: "Host": "test_url2", }, match_content=b"This is the body2", + is_optional=True, ) with httpx.Client() as client: @@ -1416,9 +1362,7 @@ def test_url_and_headers_and_content_matching(httpx_mock: HTTPXMock) -> None: assert response.content == b"" -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_headers_not_matching_and_url_and_content_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1429,6 +1373,7 @@ def test_headers_not_matching_and_url_and_content_matching( "Host": "test_url2", }, match_content=b"This is the body", + is_optional=True, ) with httpx.Client() as client: @@ -1441,9 +1386,7 @@ def test_headers_not_matching_and_url_and_content_matching( ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_and_headers_not_matching_and_content_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1454,6 +1397,7 @@ def test_url_and_headers_not_matching_and_content_matching( "Host": "test_url2", }, match_content=b"This is the body", + is_optional=True, ) with httpx.Client() as client: @@ -1466,9 +1410,7 @@ def test_url_and_headers_not_matching_and_content_matching( ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_and_headers_matching_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1479,6 +1421,7 @@ def test_url_and_headers_matching_and_content_not_matching( "Host": "test_url", }, match_content=b"This is the body2", + is_optional=True, ) with httpx.Client() as client: @@ -1491,9 +1434,7 @@ def test_url_and_headers_matching_and_content_not_matching( ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_headers_matching_and_url_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1504,6 +1445,7 @@ def test_headers_matching_and_url_and_content_not_matching( "Host": "test_url", }, match_content=b"This is the body2", + is_optional=True, ) with httpx.Client() as client: @@ -1516,9 +1458,7 @@ def test_headers_matching_and_url_and_content_not_matching( ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_matching_and_headers_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1529,6 +1469,7 @@ def test_url_matching_and_headers_and_content_not_matching( "Host": "test_url2", }, match_content=b"This is the body2", + is_optional=True, ) with httpx.Client() as client: @@ -1541,9 +1482,7 @@ def test_url_matching_and_headers_and_content_not_matching( ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_and_headers_and_content_not_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( url="https://test_url2", @@ -1552,6 +1491,7 @@ def test_url_and_headers_and_content_not_matching(httpx_mock: HTTPXMock) -> None "Host": "test_url2", }, match_content=b"This is the body2", + is_optional=True, ) with httpx.Client() as client: @@ -1577,9 +1517,7 @@ def test_method_and_url_and_headers_and_content_matching(httpx_mock: HTTPXMock) assert response.content == b"" -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_headers_not_matching_and_method_and_url_and_content_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1591,6 +1529,7 @@ def test_headers_not_matching_and_method_and_url_and_content_matching( "Host": "test_url2", }, match_content=b"This is the body", + is_optional=True, ) with httpx.Client() as client: @@ -1603,9 +1542,7 @@ def test_headers_not_matching_and_method_and_url_and_content_matching( ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_and_headers_not_matching_and_method_and_content_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1617,6 +1554,7 @@ def test_url_and_headers_not_matching_and_method_and_content_matching( "Host": "test_url2", }, match_content=b"This is the body", + is_optional=True, ) with httpx.Client() as client: @@ -1629,9 +1567,7 @@ def test_url_and_headers_not_matching_and_method_and_content_matching( ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_method_and_url_and_headers_matching_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1643,6 +1579,7 @@ def test_method_and_url_and_headers_matching_and_content_not_matching( "Host": "test_url", }, match_content=b"This is the body2", + is_optional=True, ) with httpx.Client() as client: @@ -1655,9 +1592,7 @@ def test_method_and_url_and_headers_matching_and_content_not_matching( ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_method_and_headers_matching_and_url_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1669,6 +1604,7 @@ def test_method_and_headers_matching_and_url_and_content_not_matching( "Host": "test_url", }, match_content=b"This is the body2", + is_optional=True, ) with httpx.Client() as client: @@ -1681,9 +1617,7 @@ def test_method_and_headers_matching_and_url_and_content_not_matching( ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_method_and_url_matching_and_headers_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1695,6 +1629,7 @@ def test_method_and_url_matching_and_headers_and_content_not_matching( "Host": "test_url2", }, match_content=b"This is the body2", + is_optional=True, ) with httpx.Client() as client: @@ -1707,9 +1642,7 @@ def test_method_and_url_matching_and_headers_and_content_not_matching( ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_method_matching_and_url_and_headers_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1721,6 +1654,7 @@ def test_method_matching_and_url_and_headers_and_content_not_matching( "Host": "test_url2", }, match_content=b"This is the body2", + is_optional=True, ) with httpx.Client() as client: @@ -1733,9 +1667,7 @@ def test_method_matching_and_url_and_headers_and_content_not_matching( ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_method_and_url_and_headers_and_content_not_matching( httpx_mock: HTTPXMock, ) -> None: @@ -1747,6 +1679,7 @@ def test_method_and_url_and_headers_and_content_not_matching( "Host": "test_url2", }, match_content=b"This is the body2", + is_optional=True, ) with httpx.Client() as client: @@ -1962,9 +1895,7 @@ def test_files_and_data_matching(httpx_mock: HTTPXMock) -> None: assert response.content == b"" -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_files_not_matching_name(httpx_mock: HTTPXMock, monkeypatch) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( @@ -1973,7 +1904,7 @@ def test_files_not_matching_name(httpx_mock: HTTPXMock, monkeypatch) -> None: lambda length: b"\xfb\xe4\x95\xef\xe4\xcdA\xb9A\xca\x13\xe2T\xd6\xb0\x18", ) - httpx_mock.add_response(match_files={"name2": ("file_name", b"File content")}) + httpx_mock.add_response(match_files={"name2": ("file_name", b"File content")}, is_optional=True) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -1987,9 +1918,7 @@ def test_files_not_matching_name(httpx_mock: HTTPXMock, monkeypatch) -> None: ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_files_not_matching_file_name(httpx_mock: HTTPXMock, monkeypatch) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( @@ -1998,7 +1927,7 @@ def test_files_not_matching_file_name(httpx_mock: HTTPXMock, monkeypatch) -> Non lambda length: b"\xfb\xe4\x95\xef\xe4\xcdA\xb9A\xca\x13\xe2T\xd6\xb0\x18", ) - httpx_mock.add_response(match_files={"name": ("file_name2", b"File content")}) + httpx_mock.add_response(match_files={"name": ("file_name2", b"File content")}, is_optional=True) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -2012,9 +1941,7 @@ def test_files_not_matching_file_name(httpx_mock: HTTPXMock, monkeypatch) -> Non ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_files_not_matching_content(httpx_mock: HTTPXMock, monkeypatch) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( @@ -2023,7 +1950,7 @@ def test_files_not_matching_content(httpx_mock: HTTPXMock, monkeypatch) -> None: lambda length: b"\xfb\xe4\x95\xef\xe4\xcdA\xb9A\xca\x13\xe2T\xd6\xb0\x18", ) - httpx_mock.add_response(match_files={"name": ("file_name", b"File content2")}) + httpx_mock.add_response(match_files={"name": ("file_name", b"File content2")}, is_optional=True) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -2037,9 +1964,7 @@ def test_files_not_matching_content(httpx_mock: HTTPXMock, monkeypatch) -> None: ) -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_files_matching_but_data_not_matching( httpx_mock: HTTPXMock, monkeypatch ) -> None: @@ -2053,6 +1978,7 @@ def test_files_matching_but_data_not_matching( httpx_mock.add_response( match_files={"name": ("file_name", b"File content")}, match_data={"field": "value"}, + is_optional=True, ) with httpx.Client() as client: @@ -2087,12 +2013,10 @@ def test_timeout_matching(httpx_mock: HTTPXMock) -> None: assert response.content == b"" -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_timeout_not_matching(httpx_mock: HTTPXMock) -> None: httpx_mock.add_response( - match_extensions={"timeout": {"connect": 5, "read": 5, "write": 10, "pool": 5}} + match_extensions={"timeout": {"connect": 5, "read": 5, "write": 10, "pool": 5}}, is_optional=True ) with httpx.Client() as client: @@ -2115,11 +2039,9 @@ def test_extensions_matching(httpx_mock: HTTPXMock) -> None: assert response.content == b"" -@pytest.mark.httpx_mock( - assert_all_responses_were_requested=False, assert_all_requests_were_expected=False -) +@pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_extensions_not_matching(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(match_extensions={"test": "value"}) + httpx_mock.add_response(match_extensions={"test": "value"}, is_optional=True) with httpx.Client() as client: with pytest.raises(httpx.TimeoutException) as exception_info: @@ -2129,3 +2051,55 @@ def test_extensions_not_matching(httpx_mock: HTTPXMock) -> None: == """No response can be found for GET request on https://test_url with {'test': 'value2'} extensions amongst: - Match any request with {'test': 'value'} extensions""" ) + + +def test_optional_response_not_matched(httpx_mock: HTTPXMock) -> None: + # This response is optional and the fact that it was never requested should not trigger anything + httpx_mock.add_response(url="https://test_url", is_optional=True) + httpx_mock.add_response(url="https://test_url2") + + with httpx.Client() as client: + response = client.get("https://test_url2") + assert response.content == b"" + + +def test_optional_response_matched(httpx_mock: HTTPXMock) -> None: + # This response is optional and the fact that it was never requested should not trigger anything + httpx_mock.add_response(url="https://test_url", is_optional=True) + httpx_mock.add_response(url="https://test_url2") + + with httpx.Client() as client: + response1 = client.get("https://test_url") + response2 = client.get("https://test_url2") + assert response1.content == b"" + assert response2.content == b"" + + +@pytest.mark.httpx_mock(assert_all_responses_were_requested=False) +def test_mandatory_response_matched(httpx_mock: HTTPXMock) -> None: + # This response is optional and the fact that it was never requested should not trigger anything + httpx_mock.add_response(url="https://test_url") + # This response MUST be requested (overrides global settings via marker) + httpx_mock.add_response(url="https://test_url2", is_optional=False) + + with httpx.Client() as client: + response = client.get("https://test_url2") + assert response.content == b"" + + +def test_multi_response_matched_once(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response(url="https://test_url", is_reusable=True) + + with httpx.Client() as client: + response = client.get("https://test_url") + assert response.content == b"" + + +def test_multi_response_matched_twice(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response(url="https://test_url", is_reusable=True) + + with httpx.Client() as client: + response1 = client.get("https://test_url") + response2 = client.get("https://test_url") + assert response1.content == b"" + assert response2.content == b"" diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 59d1ca9..15de08e 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -685,3 +685,59 @@ def test_invalid_marker(httpx_mock): result = testdir.runpytest() result.assert_outcomes(errors=1) result.stdout.re_match_lines([r".*got an unexpected keyword argument 'foo'"]) + + +def test_mandatory_response_not_matched(testdir: Testdir) -> None: + """ + is_optional MUST take precedence over assert_all_responses_were_requested. + """ + testdir.makepyfile( + """ + import httpx + import pytest + + @pytest.mark.httpx_mock(assert_all_responses_were_requested=False) + def test_mandatory_response_not_matched(httpx_mock): + # This response is optional and the fact that it was never requested should not trigger anything + httpx_mock.add_response(url="https://test_url") + # This response MUST be requested + httpx_mock.add_response(url="https://test_url2", is_optional=False) + + """ + ) + result = testdir.runpytest() + result.assert_outcomes(errors=1, passed=1) + # Assert the teardown assertion failure + result.stdout.fnmatch_lines( + [ + "*AssertionError: The following responses are mocked but not requested:", + "* - Match any request on https://test_url2", + "* ", + "* If this is on purpose, refer to https://github.com/Colin-b/pytest_httpx/blob/master/README.md#allow-to-register-more-responses-than-what-will-be-requested", + ], + consecutive=True, + ) + + +def test_reusable_response_not_matched(testdir: Testdir) -> None: + testdir.makepyfile( + """ + import httpx + + def test_reusable_response_not_matched(httpx_mock): + httpx_mock.add_response(url="https://test_url2", is_reusable=True) + + """ + ) + result = testdir.runpytest() + result.assert_outcomes(errors=1, passed=1) + # Assert the teardown assertion failure + result.stdout.fnmatch_lines( + [ + "*AssertionError: The following responses are mocked but not requested:", + "* - Match every request on https://test_url2", + "* ", + "* If this is on purpose, refer to https://github.com/Colin-b/pytest_httpx/blob/master/README.md#allow-to-register-more-responses-than-what-will-be-requested", + ], + consecutive=True, + )