diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bf73ffd..a80afbb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,7 +26,7 @@ jobs: pre-commit run --all-files - name: Test run: | - pytest --cov-report=xml --junitxml=junit.xml -o junit_family=legacy + tox -- --cov-report=xml --junitxml=junit.xml -o junit_family=legacy - name: Upload test results to Codecov uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 3584e33..3d7aceb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `match_params` is now handling `bool`, `int` and `float` values in addition to `str`. Only str values were previously expected. - A meaningful error will now be returned when a callback that does not return an `httpx.Response` is called. +### Added +- Support for httpx2 is implemented. When both `httpx` and `httpx2` are installed, the `httpx_mock` + fixture now mocks both of them within the same test session. + +### Changed +- `httpx` is not installed automatically anymore. You now have to require it explicitly through the `httpx` or `httpx2` extra. + Both extras can be installed at the same time to mock both libraries simultaneously. + ### Removed - python `3.10` is not supported anymore. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1192ab8..953c5d5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -28,12 +28,13 @@ Title should be a small sentence describing the request. 1) Create a new branch based on `develop` branch. 2) Fetch all dependencies using [`pip`](https://pip.pypa.io/en/stable/). ```shell - python -m pip install .[testing] + python -m pip install .[dev] ``` -3) Ensure tests are ok by running them using [`pytest`](https://doc.pytest.org/en/latest/index.html). +3) Ensure tests are ok by running them using [`tox`](https://tox.wiki/en/4.65.4/index.html). ```shell - pytest + tox ``` + You can also run tests directly using [`pytest`](https://doc.pytest.org/en/latest/index.html). 4) Install [pre-commit](https://pre-commit.com) hook to ensure new code will follow guidelines. ```shell python -m pip install pre-commit diff --git a/README.md b/README.md index 33ff065..180055e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,23 @@ > > However, current state can be considered as stable. -Once installed, `httpx_mock` [`pytest`](https://docs.pytest.org/en/latest/) fixture will make sure every [`httpx`](https://www.python-httpx.org) request will be replied to with user provided responses ([unless some hosts are explicitly skipped](#do-not-mock-some-requests)). +Since `pytest-httpx` supports both `httpx` and `httpx2`, it doesn't install any of them by itself, +you have to pick which one (or both) you are using: + +```bash +# For httpx. +pip install pytest-httpx[httpx] +# For httpx2. +pip install pytest-httpx[httpx2] +# For both. +pip install pytest-httpx[httpx,httpx2] +``` + +Once installed, `httpx_mock` [`pytest`](https://docs.pytest.org/en/latest/) fixture will make sure every [`httpx`](https://www.python-httpx.org) or [`httpx2`](https://pydantic.dev/docs/httpx2/get-started/) +request will be replied to with user provided responses ([unless some hosts are explicitly skipped](#do-not-mock-some-requests)). + +For brevity, both httpx and httpx2 will be further referred to as httpx. All the examples using httpx +should stay intact when using httpx2. - [Add responses](#add-responses) - [JSON body](#add-json-response) @@ -37,7 +53,7 @@ Once installed, `httpx_mock` [`pytest`](https://docs.pytest.org/en/latest/) fixt ## Add responses -You can register responses for both sync and async [`HTTPX`](https://www.python-httpx.org) requests. +You can register responses for both sync and async ([`HTTPX`](https://www.python-httpx.org)) requests. ```python import pytest diff --git a/pyproject.toml b/pyproject.toml index 5018df2..6369409 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,7 +39,6 @@ classifiers = [ "Typing :: Typed", ] dependencies = [ - "httpx==0.28.*", "pytest==9.*", ] dynamic = ["version"] @@ -51,12 +50,18 @@ changelog = "https://github.com/Colin-b/pytest_httpx/blob/master/CHANGELOG.md" issues = "https://github.com/Colin-b/pytest_httpx/issues" [project.optional-dependencies] +httpx = ["httpx==0.28.*"] +httpx2 = ["httpx2==2.*"] testing = [ # Used to check coverage "pytest-cov==7.*", # Used to run async tests "pytest-asyncio==1.*", ] +dev = [ + "pytest-httpx[testing,httpx,httpx2]", + "tox==4.*" +] [project.entry-points.pytest11] pytest_httpx = "pytest_httpx" diff --git a/pytest_httpx/__init__.py b/pytest_httpx/__init__.py index 8460cb0..95ce3cc 100644 --- a/pytest_httpx/__init__.py +++ b/pytest_httpx/__init__.py @@ -1,67 +1,78 @@ from collections.abc import Generator from operator import methodcaller -import httpx import pytest from pytest import Config, FixtureRequest, MonkeyPatch +from pytest_httpx._httpx_compat import HTTPXBackend, get_backends from pytest_httpx._httpx_internals import IteratorStream from pytest_httpx._httpx_mock import HTTPXMock from pytest_httpx._options import _HTTPXMockOptions from pytest_httpx.version import __version__ __all__ = ( + "HTTPXBackend", "HTTPXMock", "IteratorStream", "__version__", + "get_backends", ) -@pytest.fixture -def httpx_mock( +def _mock_backend( monkeypatch: MonkeyPatch, - request: FixtureRequest, -) -> Generator[HTTPXMock, None, None]: - httpx_mock_markers: dict = {} - for marker in request.node.iter_markers("httpx_mock"): - httpx_mock_markers = marker.kwargs | httpx_mock_markers - __tracebackhide__ = methodcaller("errisinstance", TypeError) - options = _HTTPXMockOptions(**httpx_mock_markers) - - mock = HTTPXMock(options) - + options: _HTTPXMockOptions, + mock: HTTPXMock, + backend: HTTPXBackend, +) -> None: # Mock synchronous requests - real_handle_request = httpx.HTTPTransport.handle_request + real_handle_request = backend.httpx.HTTPTransport.handle_request - def mocked_handle_request( - transport: httpx.HTTPTransport, request: httpx.Request - ) -> httpx.Response: + def mocked_handle_request(transport, request): # type: ignore[no-untyped-def] if options.should_mock(request): - return mock._handle_request(transport, request) + return mock._handle_request(backend, transport, request) return real_handle_request(transport, request) monkeypatch.setattr( - httpx.HTTPTransport, + backend.httpx.HTTPTransport, "handle_request", mocked_handle_request, ) # Mock asynchronous requests - real_handle_async_request = httpx.AsyncHTTPTransport.handle_async_request + real_handle_async_request = backend.httpx.AsyncHTTPTransport.handle_async_request - async def mocked_handle_async_request( - transport: httpx.AsyncHTTPTransport, request: httpx.Request - ) -> httpx.Response: + async def mocked_handle_async_request(transport, request): # type: ignore[no-untyped-def] if options.should_mock(request): - return await mock._handle_async_request(transport, request) + return await mock._handle_async_request(backend, transport, request) return await real_handle_async_request(transport, request) monkeypatch.setattr( - httpx.AsyncHTTPTransport, + backend.httpx.AsyncHTTPTransport, "handle_async_request", mocked_handle_async_request, ) + +@pytest.fixture +def httpx_mock( + monkeypatch: MonkeyPatch, + request: FixtureRequest, +) -> Generator[HTTPXMock, None, None]: + httpx_mock_markers: dict = {} + + for marker in request.node.iter_markers("httpx_mock"): + httpx_mock_markers = marker.kwargs | httpx_mock_markers + + __tracebackhide__ = methodcaller("errisinstance", TypeError) + options = _HTTPXMockOptions(**httpx_mock_markers) + + mock = HTTPXMock(options) + + # Mock sync and async transports of every installed httpx backend. + for backend in get_backends(): + _mock_backend(monkeypatch, options, mock, backend) + yield mock try: mock._assert_options() diff --git a/pytest_httpx/_httpx_compat.py b/pytest_httpx/_httpx_compat.py new file mode 100644 index 0000000..bb0038f --- /dev/null +++ b/pytest_httpx/_httpx_compat.py @@ -0,0 +1,65 @@ +from dataclasses import dataclass +from functools import cache +from types import ModuleType +from typing import TYPE_CHECKING, Literal + + +@dataclass(frozen=True) +class HTTPXBackend: + name: Literal["httpx", "httpx2"] + httpx: ModuleType + httpcore: ModuleType + + +@cache +def get_backends() -> list[HTTPXBackend]: + """Return every httpx backend found in the environment.""" + + backends = [] + + try: + import httpcore2 as httpcore2_ # pragma: no cover + import httpx2 as httpx2_ # pragma: no cover + except ModuleNotFoundError: # pragma: no cover + pass + else: + backends.append(HTTPXBackend("httpx2", httpx2_, httpcore2_)) # pragma: no cover + + try: + import httpcore as httpcore_ # pragma: no cover + import httpx as httpx_ # pragma: no cover + except ModuleNotFoundError: # pragma: no cover + pass + else: + backends.append(HTTPXBackend("httpx", httpx_, httpcore_)) # pragma: no cover + + if not backends: # pragma: no cover + raise RuntimeError( + "pytest-httpx requires either httpx or httpx2 to be installed.\n" + "They are now optional dependencies, install the matching extra with:\n" + " $ pip install pytest-httpx[httpx2]\n" + "or:\n" + " $ pip install pytest-httpx[httpx]\n" + ) + + return backends + + +_BACKEND_ATTRIBUTE = "__pytest_httpx_backend" + + +def set_backend(request: "httpx.Request", backend: HTTPXBackend) -> None: + # Store the intercepting backend on the request so that backend-agnostic + # callbacks (e.g. the one built by `add_response`) can build a response + # with the matching library. + setattr(request, _BACKEND_ATTRIBUTE, backend) + + +def get_backend(request: "httpx.Request") -> HTTPXBackend: + return getattr(request, _BACKEND_ATTRIBUTE) + + +if TYPE_CHECKING: + import httpcore # noqa: F401 + import httpx + import httpx2 # noqa: F401 diff --git a/pytest_httpx/_httpx_internals.py b/pytest_httpx/_httpx_internals.py index 11fe1c0..c0c953a 100644 --- a/pytest_httpx/_httpx_internals.py +++ b/pytest_httpx/_httpx_internals.py @@ -1,42 +1,54 @@ import base64 from collections.abc import AsyncIterator, Iterable, Iterator, Sequence -from typing import TypeAlias +from typing import TYPE_CHECKING, TypeAlias, Union -import httpcore -import httpx +from pytest_httpx._httpx_compat import HTTPXBackend, get_backends -# TODO Get rid of this internal import -from httpx._content import AsyncIteratorByteStream, IteratorByteStream +if TYPE_CHECKING: + from pytest_httpx._httpx_compat import httpcore, httpx, httpx2 # Those types are internally defined within httpx._types -HeaderTypes: TypeAlias = ( - httpx.Headers - | dict[str, str] - | dict[bytes, bytes] - | Sequence[tuple[str, str]] - | Sequence[tuple[bytes, bytes]] -) -PrimitiveData: TypeAlias = str | int | float | bool | None +HeaderTypes: TypeAlias = Union[ + "httpx.Headers", + "httpx2.Headers", + dict[str, str], + dict[bytes, bytes], + Sequence[tuple[str, str]], + Sequence[tuple[bytes, bytes]], +] +PrimitiveData: TypeAlias = str | int | float | bool -class IteratorStream(AsyncIteratorByteStream, IteratorByteStream): +def _iterator_stream_bases() -> list[type]: + bases: list[type] = [] + + for backend in get_backends(): + bases.append(backend.httpx.SyncByteStream) + bases.append(backend.httpx.AsyncByteStream) + + return bases + + +class IteratorStream(*_iterator_stream_bases()): # type: ignore[misc] def __init__(self, stream: Iterable[bytes]): - class Stream: - def __iter__(self) -> Iterator[bytes]: - yield from stream + self._stream = stream - async def __aiter__(self) -> AsyncIterator[bytes]: - for chunk in stream: - yield chunk + def __iter__(self) -> Iterator[bytes]: + yield from self._stream - AsyncIteratorByteStream.__init__(self, stream=Stream()) - IteratorByteStream.__init__(self, stream=Stream()) + async def __aiter__(self) -> AsyncIterator[bytes]: + for chunk in self._stream: + yield chunk -def _to_httpx_url(url: httpcore.URL, headers: list[tuple[bytes, bytes]]) -> httpx.URL: +def _to_httpx_url( + backend: HTTPXBackend, + url: "httpcore.URL", + headers: list[tuple[bytes, bytes]], +) -> "httpx.URL": for name, value in headers: if b"Proxy-Authorization" == name: - return httpx.URL( + return backend.httpx.URL( scheme=url.scheme.decode(), host=url.host.decode(), port=url.port, @@ -44,7 +56,7 @@ def _to_httpx_url(url: httpcore.URL, headers: list[tuple[bytes, bytes]]) -> http userinfo=base64.b64decode(value[6:]), ) - return httpx.URL( + return backend.httpx.URL( scheme=url.scheme.decode(), host=url.host.decode(), port=url.port, @@ -53,10 +65,12 @@ def _to_httpx_url(url: httpcore.URL, headers: list[tuple[bytes, bytes]]) -> http def _proxy_url( - real_transport: httpx.HTTPTransport | httpx.AsyncHTTPTransport, -) -> httpx.URL | None: + backend: HTTPXBackend, + real_transport: "httpx.HTTPTransport | httpx.AsyncHTTPTransport", +) -> "httpx.URL | None": if isinstance( - real_pool := real_transport._pool, (httpcore.HTTPProxy, httpcore.AsyncHTTPProxy) + real_pool := real_transport._pool, + (backend.httpcore.HTTPProxy, backend.httpcore.AsyncHTTPProxy), ): - return _to_httpx_url(real_pool._proxy_url, real_pool._proxy_headers) + return _to_httpx_url(backend, real_pool._proxy_url, real_pool._proxy_headers) return None diff --git a/pytest_httpx/_httpx_mock.py b/pytest_httpx/_httpx_mock.py index 5b7468d..58c58c1 100644 --- a/pytest_httpx/_httpx_mock.py +++ b/pytest_httpx/_httpx_mock.py @@ -1,15 +1,29 @@ import copy import inspect from collections.abc import Awaitable, Callable -from typing import Any - -import httpx +from typing import TYPE_CHECKING, Any, TypeAlias from pytest_httpx import _httpx_internals +from pytest_httpx._httpx_compat import HTTPXBackend, get_backend, set_backend from pytest_httpx._options import _HTTPXMockOptions from pytest_httpx._pretty_print import RequestDescription from pytest_httpx._request_matcher import _RequestMatcher +if TYPE_CHECKING: + from pytest_httpx._httpx_compat import httpx, httpx2 + + +Callback: TypeAlias = ( + Callable[ + ["httpx.Request"], + "httpx.Response | None | Awaitable[httpx.Response | None]", + ] + | Callable[ + ["httpx2.Request"], + "httpx2.Response | None | Awaitable[httpx2.Response | None]", + ] +) + class HTTPXMock: """ @@ -25,10 +39,7 @@ def __init__(self, options: _HTTPXMockOptions) -> None: self._callbacks: list[ tuple[ _RequestMatcher, - Callable[ - [httpx.Request], - httpx.Response | None | Awaitable[httpx.Response | None], - ], + Callback, ] ] = [] self._requests_not_matched: list[httpx.Request] = [] @@ -74,8 +85,11 @@ def add_response( json = copy.deepcopy(json) if json is not None else None - def response_callback(request: httpx.Request) -> httpx.Response: - return httpx.Response( + def response_callback(request: "httpx.Request") -> "httpx.Response": + # Build the response with the backend that intercepted the request + # so that its type matches the client. + backend = get_backend(request) + return backend.httpx.Response( status_code=status_code, extensions={"http_version": http_version.encode("ascii")}, headers=headers, @@ -90,10 +104,7 @@ def response_callback(request: httpx.Request) -> httpx.Response: def add_callback( self, - callback: Callable[ - [httpx.Request], - httpx.Response | None | Awaitable[httpx.Response | None], - ], + callback: Callback, **matchers: Any, ) -> None: """ @@ -139,8 +150,10 @@ def add_exception(self, exception: BaseException, **matchers: Any) -> None: :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: - if isinstance(exception, httpx.RequestError): + def exception_callback(request: "httpx.Request") -> None: + backend = get_backend(request) + + if isinstance(exception, backend.httpx.RequestError): exception.request = request raise exception @@ -148,9 +161,14 @@ def exception_callback(request: httpx.Request) -> None: def _handle_request( self, - real_transport: httpx.HTTPTransport, - request: httpx.Request, - ) -> httpx.Response: + backend: HTTPXBackend, + real_transport: "httpx.HTTPTransport", + request: "httpx.Request", + ) -> "httpx.Response": + # Save the backend that intercepted this request so that callbacks + # use the matching library. + set_backend(request, backend) + # Store the content in request for future matching request.read() self._requests.append((real_transport, request)) @@ -159,7 +177,7 @@ def _handle_request( if callback: response = callback(request) - if isinstance(response, httpx.Response): + if isinstance(response, backend.httpx.Response): return _unread(response) raise self._request_not_matched( @@ -175,9 +193,14 @@ def _handle_request( async def _handle_async_request( self, - real_transport: httpx.AsyncHTTPTransport, - request: httpx.Request, - ) -> httpx.Response: + backend: HTTPXBackend, + real_transport: "httpx.AsyncHTTPTransport", + request: "httpx.Request", + ) -> "httpx.Response": + # Save the backend that intercepted this request so that callbacks + # use the matching library. + set_backend(request, backend) + # Store the content in request for future matching await request.aread() self._requests.append((real_transport, request)) @@ -189,7 +212,7 @@ async def _handle_async_request( if inspect.isawaitable(response): response = await response - if isinstance(response, httpx.Response): + if isinstance(response, backend.httpx.Response): return _unread(response) raise self._request_not_matched( @@ -204,22 +227,24 @@ async def _handle_async_request( ) def _request_not_matched( - self, request: httpx.Request, message: str - ) -> httpx.TimeoutException: + self, request: "httpx.Request", message: str + ) -> "httpx.TimeoutException": + backend = get_backend(request) self._requests_not_matched.append(request) - return httpx.TimeoutException(message, request=request) + return backend.httpx.TimeoutException(message, request=request) def _explain_that_callback_must_return_a_response( self, - real_transport: httpx.BaseTransport | httpx.AsyncBaseTransport, - request: httpx.Request, + real_transport: "httpx.HTTPTransport | httpx.AsyncHTTPTransport", + request: "httpx.Request", ) -> str: - return f"Callback registered for {RequestDescription(real_transport, request, [])} MUST return httpx.Response" + backend = get_backend(request) + return f"Callback registered for {RequestDescription(real_transport, request, [])} MUST return {backend.name}.Response" def _explain_that_no_response_was_found( self, - real_transport: httpx.BaseTransport | httpx.AsyncBaseTransport, - request: httpx.Request, + real_transport: "httpx.HTTPTransport | httpx.AsyncHTTPTransport", + request: "httpx.Request", ) -> str: matchers = [matcher for matcher, _ in self._callbacks] @@ -247,14 +272,9 @@ def _explain_that_no_response_was_found( def _get_callback( self, - real_transport: httpx.HTTPTransport | httpx.AsyncHTTPTransport, - request: httpx.Request, - ) -> ( - Callable[ - [httpx.Request], httpx.Response | None | Awaitable[httpx.Response | None] - ] - | None - ): + real_transport: "httpx.HTTPTransport | httpx.AsyncHTTPTransport", + request: "httpx.Request", + ) -> Callback | None: callbacks = [ (matcher, callback) for matcher, callback in self._callbacks @@ -280,7 +300,7 @@ def _get_callback( # All callbacks have already been matched and last registered cannot be reused return None - def get_requests(self, **matchers: Any) -> list[httpx.Request]: + def get_requests(self, **matchers: Any) -> list["httpx.Request | httpx2.Request"]: """ Return all requests sent that match (empty list if no requests were matched). @@ -304,7 +324,7 @@ def get_requests(self, **matchers: Any) -> list[httpx.Request]: if matcher.match(real_transport, request) ] - def get_request(self, **matchers: Any) -> httpx.Request | None: + def get_request(self, **matchers: Any) -> "httpx.Request | httpx2.Request | None": """ Return the single request that match (or None). @@ -363,7 +383,7 @@ def _assert_options(self) -> None: ) -def _unread(response: httpx.Response) -> httpx.Response: +def _unread(response: "httpx.Response") -> "httpx.Response": # Allow to read the response on client side response.is_stream_consumed = False response.is_closed = False diff --git a/pytest_httpx/_options.py b/pytest_httpx/_options.py index 7f13ee5..b623cba 100644 --- a/pytest_httpx/_options.py +++ b/pytest_httpx/_options.py @@ -1,6 +1,8 @@ from collections.abc import Callable +from typing import TYPE_CHECKING -import httpx +if TYPE_CHECKING: + from pytest_httpx._httpx_compat import httpx, httpx2 class _HTTPXMockOptions: @@ -10,7 +12,9 @@ def __init__( assert_all_responses_were_requested: bool = True, assert_all_requests_were_expected: bool = True, can_send_already_matched_responses: bool = False, - should_mock: Callable[[httpx.Request], bool] = lambda request: True, + should_mock: Callable[ + ["httpx.Request | httpx2.Request"], bool + ] = lambda request: True, ) -> None: self.assert_all_responses_were_requested = assert_all_responses_were_requested self.assert_all_requests_were_expected = assert_all_requests_were_expected diff --git a/pytest_httpx/_pretty_print.py b/pytest_httpx/_pretty_print.py index 60af8cd..c1b0c6e 100644 --- a/pytest_httpx/_pretty_print.py +++ b/pytest_httpx/_pretty_print.py @@ -1,16 +1,21 @@ -import httpx +from typing import TYPE_CHECKING +from pytest_httpx._httpx_compat import get_backend from pytest_httpx._httpx_internals import _proxy_url from pytest_httpx._request_matcher import _RequestMatcher +if TYPE_CHECKING: + from pytest_httpx._httpx_compat import httpx + class RequestDescription: def __init__( self, - real_transport: httpx.BaseTransport | httpx.AsyncBaseTransport, - request: httpx.Request, + real_transport: "httpx.HTTPTransport | httpx.AsyncHTTPTransport", + request: "httpx.Request", matchers: list[_RequestMatcher], ): + self.backend = get_backend(request) self.real_transport = real_transport self.request = request @@ -58,7 +63,7 @@ def extra_request_description(self) -> str: extra_description.append(f"{self.request.read()} body") if self.expect_proxy: - proxy_url = _proxy_url(self.real_transport) + proxy_url = _proxy_url(self.backend, self.real_transport) extra_description.append(f"{proxy_url if proxy_url else 'no'} proxy URL") if self.expected_extensions: diff --git a/pytest_httpx/_request_matcher.py b/pytest_httpx/_request_matcher.py index b73a01f..1ac4c80 100644 --- a/pytest_httpx/_request_matcher.py +++ b/pytest_httpx/_request_matcher.py @@ -1,16 +1,17 @@ import json import re from collections.abc import Sequence -from re import Pattern -from typing import Any +from typing import TYPE_CHECKING, Any from unittest.mock import ANY -import httpx -from httpx import QueryParams - +from pytest_httpx import get_backends +from pytest_httpx._httpx_compat import HTTPXBackend, get_backend from pytest_httpx._httpx_internals import PrimitiveData, _proxy_url from pytest_httpx._options import _HTTPXMockOptions +if TYPE_CHECKING: + from pytest_httpx._httpx_compat import httpx, httpx2 + def convert_back_mock_any(original: dict, new: dict) -> None: """ @@ -30,17 +31,23 @@ def convert_back_mock_any(original: dict, new: dict) -> None: def _url_match( - url_to_match: Pattern[str] | httpx.URL, - received: httpx.URL, + backend: HTTPXBackend, + url_to_match: "re.Pattern[str] | httpx.URL", + received: "httpx.URL", params: dict[str, PrimitiveData | Sequence[PrimitiveData]] | None, ) -> bool: if isinstance(url_to_match, re.Pattern): return url_to_match.match(str(received)) is not None + # The expected URL may have been built with a different backend than the one + # that received the request, so normalize it to the request's backend before + # comparing. + url_to_match = backend.httpx.URL(str(url_to_match)) + # Compare query parameters apart as order of parameters should not matter received_params = to_params_dict(received.params) expected_params = to_params_dict( - url_to_match.params if params is None else QueryParams(params) + url_to_match.params if params is None else backend.httpx.QueryParams(params) ) if params: convert_back_mock_any(params, expected_params) @@ -52,7 +59,9 @@ def _url_match( return (received_params == expected_params) and (url == received_url) -def to_params_dict(params: QueryParams) -> dict[str, str | list[str]]: +def to_params_dict( + params: "httpx.QueryParams", +) -> dict[str, str | list[str]]: """Convert query parameters to a dict where the value is a string if the parameter has a single value and a list of string otherwise.""" d = {} for key in params: @@ -65,9 +74,9 @@ class _RequestMatcher: def __init__( self, options: _HTTPXMockOptions, - url: str | Pattern[str] | httpx.URL | None = None, + url: "str | re.Pattern[str] | httpx.URL | httpx2.URL | None" = None, method: str | None = None, - proxy_url: str | Pattern[str] | httpx.URL | None = None, + proxy_url: "str | re.Pattern[str] | httpx.URL | httpx2.URL | None" = None, match_headers: dict[str, Any] | None = None, match_content: bytes | None = None, match_json: Any | None = None, @@ -80,7 +89,7 @@ def __init__( ): self._options = options self.nb_calls = 0 - self.url = httpx.URL(url) if url and isinstance(url, str) else url + self.url = url self.method = method.upper() if method else method self.headers = match_headers self.content = match_content @@ -88,11 +97,7 @@ def __init__( self.data = match_data self.files = match_files self.params = match_params - self.proxy_url = ( - httpx.URL(proxy_url) - if proxy_url and isinstance(proxy_url, str) - else proxy_url - ) + self.proxy_url = proxy_url self.extensions = match_extensions self.is_optional = ( not options.assert_all_responses_were_requested @@ -144,11 +149,14 @@ def _is_matching_body_more_than_one_way(self) -> bool: return sum(matching_ways) > 1 def _is_matching_params_more_than_one_way(self) -> bool: - url_has_params = ( - bool(self.url.params) - if (self.url and isinstance(self.url, httpx.URL)) - else False - ) + if not self.url or isinstance(self.url, re.Pattern): + return False + + [any_backend, *_] = get_backends() + # It doesn't matter which backend to use, both + # httpx and httpx2 has the same URL params parsing logic. + url_has_params = bool(any_backend.httpx.URL(str(self.url)).params) + matching_ways = [ self.params is not None, url_has_params, @@ -157,31 +165,32 @@ def _is_matching_params_more_than_one_way(self) -> bool: def match( self, - real_transport: httpx.HTTPTransport | httpx.AsyncHTTPTransport, - request: httpx.Request, + real_transport: "httpx.HTTPTransport | httpx.AsyncHTTPTransport", + request: "httpx.Request", ) -> bool: + backend = get_backend(request) return ( - self._url_match(request) + self._url_match(backend, request) and self._method_match(request) and self._headers_match(request) - and self._content_match(request) - and self._proxy_match(real_transport) + and self._content_match(backend, request) + and self._proxy_match(backend, real_transport) and self._extensions_match(request) ) - def _url_match(self, request: httpx.Request) -> bool: + def _url_match(self, backend: HTTPXBackend, request: "httpx.Request") -> bool: if not self.url: return True - return _url_match(self.url, request.url, self.params) + return _url_match(backend, self.url, request.url, self.params) - def _method_match(self, request: httpx.Request) -> bool: + def _method_match(self, request: "httpx.Request") -> bool: if not self.method: return True return request.method == self.method - def _headers_match(self, request: httpx.Request) -> bool: + def _headers_match(self, request: "httpx.Request") -> bool: if not self.headers: return True @@ -200,7 +209,7 @@ def _headers_match(self, request: httpx.Request) -> bool: for header_name, header_value in self.headers.items() ) - def _content_match(self, request: httpx.Request) -> bool: + def _content_match(self, backend: HTTPXBackend, request: "httpx.Request") -> bool: if self.content is not None: return request.content == self.content @@ -219,7 +228,7 @@ def _content_match(self, request: httpx.Request) -> bool: # Ensure we re-use the same boundary for comparison boundary = boundary_matched.group(1) # Prevent internal httpx changes from impacting users not matching on files - from httpx._multipart import MultipartStream + MultipartStream = backend.httpx._multipart.MultipartStream multipart_content = b"".join( MultipartStream(self.data or {}, self.files, boundary) @@ -229,17 +238,19 @@ def _content_match(self, request: httpx.Request) -> bool: return True def _proxy_match( - self, real_transport: httpx.HTTPTransport | httpx.AsyncHTTPTransport + self, + backend: HTTPXBackend, + real_transport: "httpx.HTTPTransport | httpx.AsyncHTTPTransport", ) -> bool: if not self.proxy_url: return True - if real_proxy_url := _proxy_url(real_transport): - return _url_match(self.proxy_url, real_proxy_url, params=None) + if real_proxy_url := _proxy_url(backend, real_transport): + return _url_match(backend, self.proxy_url, real_proxy_url, params=None) return False - def _extensions_match(self, request: httpx.Request) -> bool: + def _extensions_match(self, request: "httpx.Request") -> bool: if not self.extensions: return True diff --git a/tests/conftest.py b/tests/conftest.py index 4c58045..b188679 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,2 +1,23 @@ # see https://docs.pytest.org/en/documentation-restructure/how-to/writing_plugins.html#testing-plugins +import pytest + +from pytest_httpx import HTTPXBackend, get_backends + pytest_plugins = ["pytester"] + + +@pytest.fixture(params=get_backends(), ids=lambda b: b.name) +def backend(request: pytest.FixtureRequest) -> HTTPXBackend: + return request.param + + +@pytest.fixture +def plugin_tests_conftest(testdir: pytest.Testdir) -> None: + testdir.makeconftest(""" + import pytest + from pytest_httpx import get_backends + + @pytest.fixture(params=get_backends(), ids=lambda b: b.name) + def backend(request): + return request.param + """) diff --git a/tests/test_backends.py b/tests/test_backends.py new file mode 100644 index 0000000..4364b7a --- /dev/null +++ b/tests/test_backends.py @@ -0,0 +1,180 @@ +import pytest + +try: + import httpx + import httpx2 +except ModuleNotFoundError: + # If one of the modules isn't installed, there's no sense running these tests. + pytest.skip( + allow_module_level=True, reason="One of http/httpx2 modules is missing." + ) + +from pytest_httpx import HTTPXMock, get_backends + + +def test_two_backends_two_names() -> None: + backends = get_backends() + names = {backend.name for backend in backends} + + assert names == {"httpx", "httpx2"} + + +def test_two_backends_are_different() -> None: + backends = get_backends() + modules = {id(backend.httpx) for backend in backends} + + assert len(modules) == 2 + + +def test_single_add_response_mocks_both_backends(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response(text="hello", is_reusable=True) + + with httpx.Client() as client: + httpx_response = client.get("https://test_url") + + with httpx2.Client() as client: + httpx2_response = client.get("https://test_url") + + assert httpx_response.text == "hello" + assert httpx2_response.text == "hello" + + +@pytest.mark.asyncio +async def test_single_add_response_mocks_both_async_backends( + httpx_mock: HTTPXMock, +) -> None: + httpx_mock.add_response(text="async hello", is_reusable=True) + + async with httpx.AsyncClient() as client: + httpx_response = await client.get("https://test_url") + + async with httpx2.AsyncClient() as client: + httpx2_response = await client.get("https://test_url") + + assert httpx_response.text == "async hello" + assert httpx2_response.text == "async hello" + + +def test_response_type_matches_backend(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_response(is_reusable=True) + + with httpx.Client() as client: + httpx_response = client.get("https://test_url") + + with httpx2.Client() as client: + httpx2_response = client.get("https://test_url") + + assert isinstance(httpx_response, httpx.Response) + assert not isinstance(httpx_response, httpx2.Response) + + assert isinstance(httpx2_response, httpx2.Response) + assert not isinstance(httpx2_response, httpx.Response) + + +def test_requests_from_both_backends_are_retrieved( + httpx_mock: HTTPXMock, +) -> None: + httpx_mock.add_response(is_reusable=True) + + with httpx.Client() as client: + client.get("https://httpx_url") + + with httpx2.Client() as client: + client.options("https://httpx2_url") + + all_requests = httpx_mock.get_requests() + + assert len(all_requests) == 2 + + httpx_request = httpx_mock.get_request(url="https://httpx_url") + + assert httpx_request + assert httpx_request.method == "GET" + + httpx2_request = httpx_mock.get_request(url="https://httpx2_url") + + assert httpx2_request + assert httpx2_request.method == "OPTIONS" + + +def test_responses_matched_in_registration_order( + httpx_mock: HTTPXMock, +) -> None: + httpx_mock.add_response(text="first") + httpx_mock.add_response(text="second") + + with httpx.Client() as client: + first_response = client.get("https://test_url") + + with httpx2.Client() as client: + second_response = client.get("https://test_url") + + assert first_response.text == "first" + assert second_response.text == "second" + + +def test_add_exception_raises_matching_backend_error(httpx_mock: HTTPXMock) -> None: + httpx_mock.add_exception( + httpx.ReadTimeout("HTTPX timeout"), url="https://httpx_url" + ) + httpx_mock.add_exception( + httpx2.ReadTimeout("HTTPX2 timeout"), url="https://httpx2_url" + ) + + with httpx.Client() as client, pytest.raises(httpx.ReadTimeout) as httpx_exc: + client.get("https://httpx_url") + + assert str(httpx_exc.value) == "HTTPX timeout" + assert httpx_exc.value.request.url == httpx.URL("https://httpx_url") + + with httpx2.Client() as client, pytest.raises(httpx2.ReadTimeout) as httpx2_exc: + client.get("https://httpx2_url") + + assert str(httpx2_exc.value) == "HTTPX2 timeout" + assert httpx2_exc.value.request.url == httpx2.URL("https://httpx2_url") + + +seen_request_modules: set[str] = set() + + +def _should_mock(request: httpx.Request | httpx2.Request) -> bool: + # Record the module of the request object so the test below could check them. + match type(request): + case httpx.Request: + seen_request_modules.add("httpx") + case httpx2.Request: + seen_request_modules.add("httpx2") + + return True + + +@pytest.mark.httpx_mock(should_mock=_should_mock) +def test_should_mock_used_by_both_backends(httpx_mock: HTTPXMock) -> None: + seen_request_modules.clear() + httpx_mock.add_response(is_reusable=True) + + with httpx.Client() as client: + client.get("https://test_url") + + with httpx2.Client() as client: + client.get("https://test_url") + + assert set(seen_request_modules) == {"httpx", "httpx2"} + + +def test_conflicting_params_backend_agnostic(httpx_mock: HTTPXMock) -> None: + with pytest.raises(ValueError) as exc_httpx: + httpx_mock.add_response( + url=httpx.URL("https://test_url?a=1"), match_params={"a": "1"} + ) + + with pytest.raises(ValueError) as exc_httpx2: + httpx_mock.add_response( + url=httpx2.URL("https://test_url?a=1"), match_params={"a": "1"} + ) + + assert ( + str(exc_httpx.value) + == str(exc_httpx2.value) + == "Provided URL must not contain any query parameter when match_params is used." + ) diff --git a/tests/test_httpx_async.py b/tests/test_httpx_async.py index f045bd8..08afe16 100644 --- a/tests/test_httpx_async.py +++ b/tests/test_httpx_async.py @@ -4,22 +4,27 @@ import re import time from collections.abc import AsyncIterable -from typing import Any +from typing import TYPE_CHECKING, Any from unittest.mock import ANY -import httpx import pytest from pytest import Testdir import pytest_httpx -from pytest_httpx import HTTPXMock +from pytest_httpx import HTTPXBackend, HTTPXMock, get_backends + +if TYPE_CHECKING: + try: + import httpx + except ModuleNotFoundError: + import httpx2 as httpx @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_without_response(httpx_mock: HTTPXMock) -> None: +async def test_without_response(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: with pytest.raises(Exception) as exception_info: - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.get("https://test_url") assert ( str(exception_info.value) @@ -28,31 +33,33 @@ async def test_without_response(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_default_response(httpx_mock: HTTPXMock) -> None: +async def test_default_response(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response() - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.content == b"" assert response.status_code == 200 - assert response.headers == httpx.Headers({}) + assert response.headers == backend.httpx.Headers({}) assert response.http_version == "HTTP/1.1" @pytest.mark.asyncio -async def test_url_matching(httpx_mock: HTTPXMock) -> None: +async def test_url_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(url="https://test_url") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.content == b"" @pytest.mark.asyncio -async def test_url_matching_reusing_response(httpx_mock: HTTPXMock) -> None: +async def test_url_matching_reusing_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url", is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.content == b"" @@ -61,10 +68,12 @@ async def test_url_matching_reusing_response(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_url_query_string_matching(httpx_mock: HTTPXMock) -> None: +async def test_url_query_string_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url?a=1&b=2", is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url?a=1&b=2") assert response.content == b"" @@ -73,13 +82,12 @@ async def test_url_query_string_matching(httpx_mock: HTTPXMock) -> None: assert response.content == b"" -@pytest.mark.asyncio @pytest.mark.parametrize("sequence_type", [list, tuple]) async def test_url_query_params_partial_matching( - httpx_mock: HTTPXMock, sequence_type: type + httpx_mock: HTTPXMock, backend: HTTPXBackend, sequence_type: type ) -> None: httpx_mock.add_response( - url=httpx.URL("https://test_url"), + url=backend.httpx.URL("https://test_url"), match_params={ "a": sequence_type(["1", "3"]), "b": ANY, @@ -90,7 +98,7 @@ async def test_url_query_params_partial_matching( is_reusable=True, ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url?a=1&b=2&a=3&c=4&d=5&d=6&e=7") assert response.content == b"" @@ -100,51 +108,59 @@ async def test_url_query_params_partial_matching( @pytest.mark.asyncio -async def test_url_as_pattern_ignoring_query_parameters(httpx_mock: HTTPXMock): +async def test_url_as_pattern_ignoring_query_parameters( + httpx_mock: HTTPXMock, backend: HTTPXBackend +): httpx_mock.add_response(url=re.compile("https://test_url/something.*")) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url/something?a=1&b=2") assert response.content == b"" @pytest.mark.asyncio -async def test_url_query_params_with_single_value_list(httpx_mock: HTTPXMock) -> None: +async def test_url_query_params_with_single_value_list( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", match_params={"a": ["1"]}, is_optional=True, ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url?a=1") assert response.content == b"" @pytest.mark.asyncio -async def test_url_query_params_with_non_str_name(httpx_mock: HTTPXMock) -> None: +async def test_url_query_params_with_non_str_name( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", match_params={1: "1"}, is_optional=True, ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url?1=1") assert response.content == b"" @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_url_query_params_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_url_query_params_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", match_params={"a": "1"}, is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url?a=2") assert ( str(exception_info.value) @@ -210,10 +226,11 @@ async def test_match_params_with_non_str_values_and_params_provided_as_dict( httpx_mock: HTTPXMock, match_params: dict[str, Any], request_params: dict[str, Any], + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(url="https://test_url", match_params=match_params) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url", params=request_params) assert response.content == b"" @@ -259,10 +276,11 @@ async def test_match_params_with_non_str_values_and_params_in_requested_url( httpx_mock: HTTPXMock, match_params: dict[str, Any], url: str, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(url="https://test_url", match_params=match_params) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get(url) assert response.content == b"" @@ -270,10 +288,11 @@ async def test_match_params_with_non_str_values_and_params_in_requested_url( @pytest.mark.asyncio async def test_url_matching_with_more_than_one_value_on_same_param( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(url="https://test_url?a=1&a=3", is_optional=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url", params={"a": [1, 3]}) assert response.content == b"" @@ -282,11 +301,12 @@ async def test_url_matching_with_more_than_one_value_on_same_param( @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_not_matching_with_more_than_one_value_on_same_param_and_diff_value( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(url="https://test_url?a=2&a=3", is_optional=True) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get("https://test_url", params={"a": [1, 3]}) assert ( str(exception_info.value) @@ -299,11 +319,12 @@ async def test_url_not_matching_with_more_than_one_value_on_same_param_and_diff_ @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_not_matching_with_more_than_one_value_on_same_param_and_more_values( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(url="https://test_url?a=1&a=3", is_optional=True) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get("https://test_url", params={"a": [1, 3, 4]}) assert ( str(exception_info.value) @@ -316,11 +337,12 @@ async def test_url_not_matching_with_more_than_one_value_on_same_param_and_more_ @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_not_matching_with_more_than_one_value_on_same_param_and_less_values( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(url="https://test_url?a=1&a=3&a=4", is_optional=True) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get("https://test_url", params={"a": [1, 3]}) assert ( str(exception_info.value) @@ -331,11 +353,11 @@ async def test_url_not_matching_with_more_than_one_value_on_same_param_and_less_ @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_url_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_url_not_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: 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: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get("https://test_url2") assert ( str(exception_info.value) @@ -346,11 +368,13 @@ async def test_url_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_url_query_string_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_url_query_string_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: 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: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: # Same parameter order matters as it corresponds to a list on server side await client.get("https://test_url?a=2&a=1") assert ( @@ -361,10 +385,10 @@ async def test_url_query_string_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_method_matching(httpx_mock: HTTPXMock) -> None: +async def test_method_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(method="get", is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.content == b"" @@ -374,11 +398,13 @@ async def test_method_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_method_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_method_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(method="get", is_optional=True) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url") assert ( str(exception_info.value) @@ -388,12 +414,14 @@ async def test_method_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_reusing_one_response(httpx_mock: HTTPXMock) -> None: +async def test_reusing_one_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", content=b"test content", is_reusable=True ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.content == b"test content" @@ -402,39 +430,45 @@ async def test_reusing_one_response(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_response_with_string_body(httpx_mock: HTTPXMock) -> None: +async def test_response_with_string_body( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url", text="test content") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.content == b"test content" @pytest.mark.asyncio -async def test_response_with_html_string_body(httpx_mock: HTTPXMock) -> None: +async def test_response_with_html_string_body( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url", html="test content") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.content == b"test content" @pytest.mark.asyncio -async def test_stream_response_streaming(httpx_mock: HTTPXMock) -> None: +async def test_stream_response_streaming( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> 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: + async with backend.httpx.AsyncClient() as client: async with client.stream(method="GET", url="https://test_url") as response: assert [part async for part in response.aiter_raw()] == [ b"part 1", b"part 2", ] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): async for _ in response.aiter_raw(): pass # pragma: no cover @@ -444,26 +478,28 @@ async def test_stream_response_streaming(httpx_mock: HTTPXMock) -> None: b"part 2", ] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): async for _ in response.aiter_raw(): pass # pragma: no cover @pytest.mark.asyncio -async def test_content_response_streaming(httpx_mock: HTTPXMock) -> None: +async def test_content_response_streaming( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", content=b"part 1 and 2", is_reusable=True, ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: async with client.stream(method="GET", url="https://test_url") as response: assert [part async for part in response.aiter_raw()] == [ b"part 1 and 2", ] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): async for _ in response.aiter_raw(): pass # pragma: no cover @@ -472,26 +508,28 @@ async def test_content_response_streaming(httpx_mock: HTTPXMock) -> None: b"part 1 and 2", ] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): async for _ in response.aiter_raw(): pass # pragma: no cover @pytest.mark.asyncio -async def test_text_response_streaming(httpx_mock: HTTPXMock) -> None: +async def test_text_response_streaming( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", text="part 1 and 2", is_reusable=True, ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: async with client.stream(method="GET", url="https://test_url") as response: assert [part async for part in response.aiter_raw()] == [ b"part 1 and 2", ] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): async for _ in response.aiter_raw(): pass # pragma: no cover @@ -500,38 +538,42 @@ async def test_text_response_streaming(httpx_mock: HTTPXMock) -> None: b"part 1 and 2", ] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): async for _ in response.aiter_raw(): pass # pragma: no cover @pytest.mark.asyncio -async def test_default_response_streaming(httpx_mock: HTTPXMock) -> None: +async def test_default_response_streaming( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: async with client.stream(method="GET", url="https://test_url") as response: assert [part async for part in response.aiter_raw()] == [] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): async for _ in response.aiter_raw(): pass # pragma: no cover async with client.stream(method="GET", url="https://test_url") as response: assert [part async for part in response.aiter_raw()] == [] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): async for _ in response.aiter_raw(): pass # pragma: no cover @pytest.mark.asyncio -async def test_with_many_responses(httpx_mock: HTTPXMock) -> None: +async def test_with_many_responses( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> 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") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.content == b"test content 1" @@ -543,13 +585,15 @@ async def test_with_many_responses(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_with_many_reused_responses(httpx_mock: HTTPXMock) -> None: +async def test_with_many_reused_responses( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> 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", is_reusable=True ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.content == b"test content 1" @@ -561,7 +605,9 @@ async def test_with_many_reused_responses(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_with_many_responses_methods(httpx_mock: HTTPXMock) -> None: +async def test_with_many_responses_methods( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", method="GET", content=b"test content 1" ) @@ -581,7 +627,7 @@ async def test_with_many_responses_methods(httpx_mock: HTTPXMock) -> None: url="https://test_url", method="HEAD", content=b"test content 6" ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url") assert response.content == b"test content 2" @@ -602,7 +648,9 @@ async def test_with_many_responses_methods(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_with_many_responses_status_codes(httpx_mock: HTTPXMock) -> None: +async def test_with_many_responses_status_codes( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", method="GET", content=b"test content 1", status_code=200 ) @@ -634,7 +682,7 @@ async def test_with_many_responses_status_codes(httpx_mock: HTTPXMock) -> None: status_code=500, ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url") assert response.content == b"test content 2" assert response.status_code == 201 @@ -661,7 +709,9 @@ async def test_with_many_responses_status_codes(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_with_many_responses_urls_str(httpx_mock: HTTPXMock) -> None: +async def test_with_many_responses_urls_str( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url?param1=test", method="GET", content=b"test content 1" ) @@ -681,44 +731,46 @@ async def test_with_many_responses_urls_str(httpx_mock: HTTPXMock) -> None: url="https://test_url?param6=test", method="HEAD", content=b"test content 6" ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post( - httpx.URL("https://test_url", params={"param2": "test"}) + backend.httpx.URL("https://test_url", params={"param2": "test"}) ) assert response.content == b"test content 2" response = await client.get( - httpx.URL("https://test_url", params={"param1": "test"}) + backend.httpx.URL("https://test_url", params={"param1": "test"}) ) assert response.content == b"test content 1" response = await client.put( - httpx.URL("https://test_url", params={"param3": "test"}) + backend.httpx.URL("https://test_url", params={"param3": "test"}) ) assert response.content == b"test content 3" response = await client.head( - httpx.URL("https://test_url", params={"param6": "test"}) + backend.httpx.URL("https://test_url", params={"param6": "test"}) ) assert response.content == b"test content 6" response = await client.patch( - httpx.URL("https://test_url", params={"param5": "test"}) + backend.httpx.URL("https://test_url", params={"param5": "test"}) ) assert response.content == b"test content 5" response = await client.delete( - httpx.URL("https://test_url", params={"param4": "test"}) + backend.httpx.URL("https://test_url", params={"param4": "test"}) ) assert response.content == b"test content 4" @pytest.mark.asyncio -async def test_response_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: +async def test_response_with_pattern_in_url( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url=re.compile(".*test.*")) httpx_mock.add_response(url="https://unmatched", content=b"test content") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://unmatched") assert response.content == b"test content" @@ -727,11 +779,13 @@ async def test_response_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_request_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: +async def test_request_with_pattern_in_url( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url") httpx_mock.add_response(url="https://unmatched") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.get("https://unmatched") await client.get("https://test_url", headers={"X-Test": "1"}) @@ -741,12 +795,14 @@ async def test_request_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_requests_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: +async def test_requests_with_pattern_in_url( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url") httpx_mock.add_response(url="https://tests_url") httpx_mock.add_response(url="https://unmatched") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.get("https://tests_url", headers={"X-Test": "1"}) await client.get("https://unmatched", headers={"X-Test": "2"}) await client.get("https://test_url") @@ -758,12 +814,14 @@ async def test_requests_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_callback_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: - def custom_response(request: httpx.Request) -> httpx.Response: - return httpx.Response(status_code=200, json={"url": str(request.url)}) +async def test_callback_with_pattern_in_url( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json={"url": str(request.url)}) - def custom_response2(request: httpx.Request) -> httpx.Response: - return httpx.Response( + def custom_response2(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response( status_code=200, extensions={"http_version": b"HTTP/2.0"}, json={"url": str(request.url)}, @@ -772,7 +830,7 @@ def custom_response2(request: httpx.Request) -> httpx.Response: httpx_mock.add_callback(custom_response, url=re.compile(".*test.*")) httpx_mock.add_callback(custom_response2, url="https://unmatched") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://unmatched") assert response.http_version == "HTTP/2.0" @@ -781,16 +839,18 @@ def custom_response2(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -async def test_async_callback_with_await_statement(httpx_mock: HTTPXMock) -> None: - async def simulate_network_latency(request: httpx.Request): +async def test_async_callback_with_await_statement( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + async def simulate_network_latency(request: "httpx.Request"): await asyncio.sleep(1) - return httpx.Response( + return backend.httpx.Response( status_code=200, json={"url": str(request.url), "time": time.time()}, ) - def instant_response(request: httpx.Request) -> httpx.Response: - return httpx.Response( + def instant_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response( status_code=200, json={"url": str(request.url), "time": time.time()} ) @@ -798,7 +858,7 @@ def instant_response(request: httpx.Request) -> httpx.Response: httpx_mock.add_callback(instant_response) httpx_mock.add_response(json={"url": "not a callback"}) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: responses = await asyncio.gather( client.get("https://slow"), client.get("https://fast_with_callback"), @@ -818,12 +878,16 @@ def instant_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -async def test_async_callback_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: - async def custom_response(request: httpx.Request) -> httpx.Response: - return httpx.Response(status_code=200, json={"url": str(request.url)}) +async def test_async_callback_with_pattern_in_url( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + async def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json={"url": str(request.url)}) - async def custom_response2(request: httpx.Request) -> httpx.Response: - return httpx.Response( + async def custom_response2( + request: "httpx.Request", + ) -> "httpx.Response": + return backend.httpx.Response( status_code=200, extensions={"http_version": b"HTTP/2.0"}, json={"url": str(request.url)}, @@ -832,7 +896,7 @@ async def custom_response2(request: httpx.Request) -> httpx.Response: httpx_mock.add_callback(custom_response, url=re.compile(".*test.*")) httpx_mock.add_callback(custom_response2, url="https://unmatched") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://unmatched") assert response.http_version == "HTTP/2.0" @@ -841,39 +905,42 @@ async def custom_response2(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -async def test_with_many_responses_urls_instances(httpx_mock: HTTPXMock) -> None: +async def test_with_many_responses_urls_instances( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + httpx_mock.add_response( - url=httpx.URL("https://test_url", params={"param1": "test"}), + url=backend.httpx.URL("https://test_url", params={"param1": "test"}), method="GET", content=b"test content 1", ) httpx_mock.add_response( - url=httpx.URL("https://test_url", params={"param2": "test"}), + url=backend.httpx.URL("https://test_url", params={"param2": "test"}), method="POST", content=b"test content 2", ) httpx_mock.add_response( - url=httpx.URL("https://test_url", params={"param3": "test"}), + url=backend.httpx.URL("https://test_url", params={"param3": "test"}), method="PUT", content=b"test content 3", ) httpx_mock.add_response( - url=httpx.URL("https://test_url", params={"param4": "test"}), + url=backend.httpx.URL("https://test_url", params={"param4": "test"}), method="DELETE", content=b"test content 4", ) httpx_mock.add_response( - url=httpx.URL("https://test_url", params={"param5": "test"}), + url=backend.httpx.URL("https://test_url", params={"param5": "test"}), method="PATCH", content=b"test content 5", ) httpx_mock.add_response( - url=httpx.URL("https://test_url", params={"param6": "test"}), + url=backend.httpx.URL("https://test_url", params={"param6": "test"}), method="HEAD", content=b"test content 6", ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url?param2=test") assert response.content == b"test content 2" @@ -894,35 +961,38 @@ async def test_with_many_responses_urls_instances(httpx_mock: HTTPXMock) -> None @pytest.mark.asyncio -async def test_with_http_version_2(httpx_mock: HTTPXMock) -> None: +async def test_with_http_version_2( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", http_version="HTTP/2", content=b"test content 1" ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.content == b"test content 1" assert response.http_version == "HTTP/2" @pytest.mark.asyncio -async def test_with_headers(httpx_mock: HTTPXMock) -> None: +async def test_with_headers(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( url="https://test_url", content=b"test content 1", headers={"X-Test": "Test value"}, ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.content == b"test content 1" - assert response.headers == httpx.Headers( + assert response.headers == backend.httpx.Headers( {"x-test": "Test value", "content-length": "14"} ) @pytest.mark.asyncio -async def test_requests_retrieval(httpx_mock: HTTPXMock) -> None: +async def test_requests_retrieval(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: + httpx_mock.add_response( url="https://test_url", method="GET", content=b"test content 1" ) @@ -942,7 +1012,7 @@ async def test_requests_retrieval(httpx_mock: HTTPXMock) -> None: url="https://test_url", method="HEAD", content=b"test content 6" ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.post("https://test_url", content=b"sent content 2") await client.get("https://test_url", headers={"X-TEST": "test header 1"}) await client.put("https://test_url", content=b"sent content 3") @@ -951,74 +1021,80 @@ async def test_requests_retrieval(httpx_mock: HTTPXMock) -> None: await client.delete("https://test_url", headers={"X-Test": "test header 4"}) patch_request = httpx_mock.get_request( - url=httpx.URL("https://test_url"), method="PATCH" + url=backend.httpx.URL("https://test_url"), method="PATCH" ) assert patch_request is not None assert patch_request.read() == b"sent content 5" head_request = httpx_mock.get_request( - url=httpx.URL("https://test_url"), method="HEAD" + url=backend.httpx.URL("https://test_url"), method="HEAD" ) assert head_request is not None assert head_request.read() == b"" put_request = httpx_mock.get_request( - url=httpx.URL("https://test_url"), method="PUT" + url=backend.httpx.URL("https://test_url"), method="PUT" ) assert put_request is not None assert put_request.read() == b"sent content 3" get_request = httpx_mock.get_request( - url=httpx.URL("https://test_url"), method="GET" + url=backend.httpx.URL("https://test_url"), method="GET" ) assert get_request is not None assert get_request.headers["x-test"] == "test header 1" post_request = httpx_mock.get_request( - url=httpx.URL("https://test_url"), method="POST" + url=backend.httpx.URL("https://test_url"), method="POST" ) assert post_request is not None assert post_request.read() == b"sent content 2" delete_request = httpx_mock.get_request( - url=httpx.URL("https://test_url"), method="DELETE" + url=backend.httpx.URL("https://test_url"), method="DELETE" ) assert delete_request is not None assert delete_request.headers["x-test"] == "test header 4" @pytest.mark.asyncio -async def test_requests_retrieval_on_same_url(httpx_mock: HTTPXMock) -> None: +async def test_requests_retrieval_on_same_url( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url", is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.get("https://test_url", headers={"X-TEST": "test header 1"}) await client.get("https://test_url", headers={"X-TEST": "test header 2"}) - requests = httpx_mock.get_requests(url=httpx.URL("https://test_url")) + requests = httpx_mock.get_requests(url=backend.httpx.URL("https://test_url")) assert len(requests) == 2 assert requests[0].headers["x-test"] == "test header 1" assert requests[1].headers["x-test"] == "test header 2" @pytest.mark.asyncio -async def test_request_retrieval_on_same_url(httpx_mock: HTTPXMock) -> None: +async def test_request_retrieval_on_same_url( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.get("https://test_url", headers={"X-TEST": "test header 1"}) await client.get("https://test_url2", headers={"X-TEST": "test header 2"}) - request = httpx_mock.get_request(url=httpx.URL("https://test_url")) + request = httpx_mock.get_request(url=backend.httpx.URL("https://test_url")) assert request is not None assert request.headers["x-test"] == "test header 1" @pytest.mark.asyncio -async def test_requests_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: +async def test_requests_retrieval_on_same_method( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.get("https://test_url", headers={"X-TEST": "test header 1"}) await client.get("https://test_url2", headers={"X-TEST": "test header 2"}) @@ -1029,10 +1105,12 @@ async def test_requests_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_request_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: +async def test_request_retrieval_on_same_method( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.get("https://test_url", headers={"X-TEST": "test header 1"}) await client.post("https://test_url", headers={"X-TEST": "test header 2"}) @@ -1042,26 +1120,32 @@ async def test_request_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_requests_retrieval_on_same_url_and_method(httpx_mock: HTTPXMock) -> None: +async def test_requests_retrieval_on_same_url_and_method( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.get("https://test_url", headers={"X-TEST": "test header 1"}) await client.get("https://test_url", headers={"X-TEST": "test header 2"}) await client.post("https://test_url", headers={"X-TEST": "test header 3"}) await client.get("https://test_url2", headers={"X-TEST": "test header 4"}) - requests = httpx_mock.get_requests(url=httpx.URL("https://test_url"), method="GET") + requests = httpx_mock.get_requests( + url=backend.httpx.URL("https://test_url"), method="GET" + ) assert len(requests) == 2 assert requests[0].headers["x-test"] == "test header 1" assert requests[1].headers["x-test"] == "test header 2" @pytest.mark.asyncio -async def test_default_requests_retrieval(httpx_mock: HTTPXMock) -> None: +async def test_default_requests_retrieval( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.post("https://test_url", headers={"X-TEST": "test header 1"}) await client.get("https://test_url2", headers={"X-TEST": "test header 2"}) @@ -1072,10 +1156,12 @@ async def test_default_requests_retrieval(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_default_request_retrieval(httpx_mock: HTTPXMock) -> None: +async def test_default_request_retrieval( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response() - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.post("https://test_url", headers={"X-TEST": "test header 1"}) request = httpx_mock.get_request() @@ -1084,7 +1170,7 @@ async def test_default_request_retrieval(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_requests_json_body(httpx_mock: HTTPXMock) -> None: +async def test_requests_json_body(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( url="https://test_url", method="GET", json=["list content 1", "list content 2"] ) @@ -1095,7 +1181,7 @@ async def test_requests_json_body(httpx_mock: HTTPXMock) -> None: ) httpx_mock.add_response(url="https://test_url", method="PUT", json="string value") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url") assert response.json() == {"key 1": "value 1", "key 2": "value 2"} assert response.headers["content-type"] == "application/json" @@ -1110,85 +1196,107 @@ async def test_requests_json_body(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_callback_raising_exception(httpx_mock: HTTPXMock) -> None: - def raise_timeout(request: httpx.Request) -> httpx.Response: - raise httpx.ReadTimeout( +async def test_callback_raising_exception( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + + def raise_timeout(request: "httpx.Request") -> "httpx.Response": + raise backend.httpx.ReadTimeout( f"Unable to read within {request.extensions['timeout']['read']}", request=request, ) httpx_mock.add_callback(raise_timeout, url="https://test_url") - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.ReadTimeout) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.ReadTimeout) as exception_info: await client.get("https://test_url") assert str(exception_info.value) == "Unable to read within 5.0" @pytest.mark.asyncio -async def test_async_callback_raising_exception(httpx_mock: HTTPXMock) -> None: - async def raise_timeout(request: httpx.Request) -> httpx.Response: - raise httpx.ReadTimeout( +async def test_async_callback_raising_exception( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + + async def raise_timeout(request: "httpx.Request") -> "httpx.Response": + raise backend.httpx.ReadTimeout( f"Unable to read within {request.extensions['timeout']['read']}", request=request, ) httpx_mock.add_callback(raise_timeout, url="https://test_url") - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.ReadTimeout) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.ReadTimeout) as exception_info: await client.get("https://test_url") assert str(exception_info.value) == "Unable to read within 5.0" @pytest.mark.asyncio -async def test_request_exception_raising(httpx_mock: HTTPXMock) -> None: +async def test_request_exception_raising( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_exception( - httpx.ReadTimeout("Unable to read within 5.0"), url="https://test_url" + backend.httpx.ReadTimeout("Unable to read within 5.0"), url="https://test_url" ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.ReadTimeout) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.ReadTimeout) as exception_info: await client.get("https://test_url") assert str(exception_info.value) == "Unable to read within 5.0" assert exception_info.value.request is not None +@pytest.fixture +def _non_request_exception_raising_errors( + request: pytest.FixtureRequest, backend: HTTPXBackend +) -> tuple[type[BaseException], str]: + if request.param == "http": + return backend.httpx.HTTPError, "Unable to read within 5.0" + + if request.param == "cancelled": + return asyncio.CancelledError, "Request was cancelled" + + raise ValueError( + f'Invalid param {request.param}, must be either "http" or "cancelled".' + ) + + @pytest.mark.asyncio @pytest.mark.parametrize( - ("exception_type", "message"), + "_non_request_exception_raising_errors", [ # httpx exception without request context - pytest.param( - httpx.HTTPError, "Unable to read within 5.0", id="non_request_exception" - ), + pytest.param("http", id="non_request_exception"), # BaseException derived exception - pytest.param( - asyncio.CancelledError, - "Request was cancelled", - id="cancelled_exception", - ), + pytest.param("cancelled", id="cancelled_exception"), ], + indirect=True, ) async def test_non_request_exception_raising( - httpx_mock: HTTPXMock, exception_type: type, message: str + httpx_mock: HTTPXMock, _non_request_exception_raising_errors, backend: HTTPXBackend ) -> None: - httpx_mock.add_exception(exception_type(message), url="https://test_url") + [exc_type, message] = _non_request_exception_raising_errors + httpx_mock.add_exception(exc_type(message), url="https://test_url") - async with httpx.AsyncClient() as client: - with pytest.raises(exception_type) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(exc_type) as exception_info: await client.get("https://test_url") assert str(exception_info.value) == message @pytest.mark.asyncio -async def test_callback_returning_response(httpx_mock: HTTPXMock) -> None: - def custom_response(request: httpx.Request) -> httpx.Response: - return httpx.Response(status_code=200, json={"url": str(request.url)}) +async def test_callback_returning_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + + def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json={"url": str(request.url)}) httpx_mock.add_callback(custom_response, url="https://test_url") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.json() == {"url": "https://test_url"} assert response.headers["content-type"] == "application/json" @@ -1198,16 +1306,16 @@ def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("return_value", [None, "not a response"]) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_callback_not_returning_a_response( - httpx_mock: HTTPXMock, return_value: str | None + httpx_mock: HTTPXMock, return_value: str | None, backend: HTTPXBackend ) -> None: httpx_mock.add_callback(lambda request: return_value, url="https://test_url") - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get("https://test_url") assert ( str(exception_info.value) - == "Callback registered for GET request on https://test_url MUST return httpx.Response" + == f"Callback registered for GET request on https://test_url MUST return {backend.name}.Response" ) @@ -1215,43 +1323,52 @@ async def test_callback_not_returning_a_response( @pytest.mark.parametrize("return_value", [None, "not a response"]) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_async_callback_not_returning_a_response( - httpx_mock: HTTPXMock, return_value: str | None + httpx_mock: HTTPXMock, return_value: str | None, backend: HTTPXBackend ) -> None: - async def invalid_response(request: httpx.Request) -> httpx.Response: + + async def invalid_response( + request: "httpx.Request", + ) -> "httpx.Response": return return_value httpx_mock.add_callback(invalid_response, url="https://test_url") - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get("https://test_url") assert ( str(exception_info.value) - == "Callback registered for GET request on https://test_url MUST return httpx.Response" + == f"Callback registered for GET request on https://test_url MUST return {backend.name}.Response" ) @pytest.mark.asyncio -async def test_async_callback_returning_response(httpx_mock: HTTPXMock) -> None: - async def custom_response(request: httpx.Request) -> httpx.Response: - return httpx.Response(status_code=200, json={"url": str(request.url)}) +async def test_async_callback_returning_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + + async def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json={"url": str(request.url)}) httpx_mock.add_callback(custom_response, url="https://test_url") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.json() == {"url": "https://test_url"} assert response.headers["content-type"] == "application/json" @pytest.mark.asyncio -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"]) +async def test_callback_executed_twice( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + + def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json=["content"]) httpx_mock.add_callback(custom_response, is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.json() == ["content"] assert response.headers["content-type"] == "application/json" @@ -1262,13 +1379,16 @@ def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -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"]) +async def test_async_callback_executed_twice( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + + async def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json=["content"]) httpx_mock.add_callback(custom_response, is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.json() == ["content"] assert response.headers["content-type"] == "application/json" @@ -1279,14 +1399,17 @@ async def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -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"]) +async def test_callback_registered_after_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + + def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json=["content2"]) httpx_mock.add_response(json=["content1"]) httpx_mock.add_callback(custom_response, is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.json() == ["content1"] assert response.headers["content-type"] == "application/json" @@ -1302,14 +1425,17 @@ def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -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"]) +async def test_async_callback_registered_after_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + + async def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json=["content2"]) httpx_mock.add_response(json=["content1"]) httpx_mock.add_callback(custom_response, is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.json() == ["content1"] assert response.headers["content-type"] == "application/json" @@ -1325,14 +1451,17 @@ async def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -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"]) +async def test_response_registered_after_callback( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + + def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json=["content1"]) httpx_mock.add_callback(custom_response) httpx_mock.add_response(json=["content2"], is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.json() == ["content1"] assert response.headers["content-type"] == "application/json" @@ -1348,14 +1477,17 @@ def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -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"]) +async def test_response_registered_after_async_callback( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + + async def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json=["content1"]) httpx_mock.add_callback(custom_response) httpx_mock.add_response(json=["content2"], is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.json() == ["content1"] assert response.headers["content-type"] == "application/json" @@ -1371,13 +1503,16 @@ async def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -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"]) +async def test_callback_matching_method( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + + def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json=["content"]) httpx_mock.add_callback(custom_response, method="GET", is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.json() == ["content"] assert response.headers["content-type"] == "application/json" @@ -1388,13 +1523,16 @@ def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.asyncio -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"]) +async def test_async_callback_matching_method( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + + async def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json=["content"]) httpx_mock.add_callback(custom_response, method="GET", is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.json() == ["content"] assert response.headers["content-type"] == "application/json" @@ -1408,23 +1546,29 @@ def test_request_retrieval_with_more_than_one(testdir: Testdir) -> None: """ Single request cannot be returned if there is more than one matching. """ - testdir.makepyfile(""" - import httpx + testdir.makeconftest(""" import pytest + from pytest_httpx import HTTPXBackend, get_backends + @pytest.fixture(params=get_backends(), ids=lambda b: b.name) + def backend(request): + return request.param + """) + testdir.makepyfile(""" + import pytest @pytest.mark.asyncio - async def test_request_retrieval_with_more_than_one(httpx_mock): + async def test_request_retrieval_with_more_than_one(httpx_mock, backend): httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.get("https://test_url", headers={"X-TEST": "test header 1"}) await client.get("https://test_url", headers={"X-TEST": "test header 2"}) - httpx_mock.get_request(url=httpx.URL("https://test_url")) + httpx_mock.get_request(url=backend.httpx.URL("https://test_url")) """) result = testdir.runpytest() - result.assert_outcomes(failed=1) + result.assert_outcomes(failed=len(get_backends())) result.stdout.fnmatch_lines( [ "*AssertionError: More than one request (2) matched, use get_requests instead or refine your filters." @@ -1433,21 +1577,26 @@ async def test_request_retrieval_with_more_than_one(httpx_mock): @pytest.mark.asyncio -async def test_headers_matching(httpx_mock: HTTPXMock) -> None: +async def test_headers_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: + httpx_mock.add_response( - match_headers={"User-Agent": f"python-httpx/{httpx.__version__}"} + match_headers={ + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}" + } ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.content == b"" @pytest.mark.asyncio -async def test_multi_value_headers_matching(httpx_mock: HTTPXMock) -> None: +async def test_multi_value_headers_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(match_headers={"my-custom-header": "value1, value2"}) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get( "https://test_url", headers=[("my-custom-header", "value1"), ("my-custom-header", "value2")], @@ -1459,13 +1608,14 @@ async def test_multi_value_headers_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_multi_value_headers_not_matching_single_value_issued( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: 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: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get( "https://test_url", headers=[ @@ -1484,13 +1634,14 @@ async def test_multi_value_headers_not_matching_single_value_issued( @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_multi_value_headers_not_matching_multi_value_issued( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: 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: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get( "https://test_url", headers=[ @@ -1507,41 +1658,49 @@ async def test_multi_value_headers_not_matching_multi_value_issued( @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_headers_matching_respect_case(httpx_mock: HTTPXMock) -> None: +async def test_headers_matching_respect_case( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + httpx_mock.add_response( - match_headers={"user-agent": f"python-httpx/{httpx.__version__}"}, + match_headers={ + "user-agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}" + }, is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get("https://test_url") assert ( str(exception_info.value) - == f"""No response can be found for GET request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}'}} headers amongst: -- Match any request with {{'user-agent': 'python-httpx/{httpx.__version__}'}} headers""" + == f"""No response can be found for GET request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers amongst: +- Match any request with {{'user-agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers""" ) @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_headers_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_headers_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + httpx_mock.add_response( match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", "Host2": "test_url", }, is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get("https://test_url") assert ( str(exception_info.value) - == f"""No response can be found for GET request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers amongst: -- Match any request with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2', 'Host2': 'test_url'}} headers""" + == f"""No response can be found for GET request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers amongst: +- Match any request with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2', 'Host2': 'test_url'}} headers""" ) @@ -1549,6 +1708,7 @@ async def test_headers_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_not_matching_upper_case_headers_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( method="GET", @@ -1556,8 +1716,8 @@ async def test_url_not_matching_upper_case_headers_matching( match_headers={"MyHeader": "Something"}, is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get("https://test_url", headers={"MyHeader": "Something"}) assert ( str(exception_info.value) @@ -1567,29 +1727,37 @@ async def test_url_not_matching_upper_case_headers_matching( @pytest.mark.asyncio -async def test_content_matching(httpx_mock: HTTPXMock) -> None: +async def test_content_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(match_content=b"This is the body") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url", content=b"This is the body") assert response.read() == b"" @pytest.mark.asyncio -async def test_proxy_matching_with_authentication(httpx_mock: HTTPXMock) -> None: +async def test_proxy_matching_with_authentication( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(proxy_url="http://user:pwd@my_other_proxy/") - async with httpx.AsyncClient(proxy="http://user:pwd@my_other_proxy") as client: + async with backend.httpx.AsyncClient( + proxy="http://user:pwd@my_other_proxy" + ) as client: response = await client.get("https://test_url") assert response.read() == b"" @pytest.mark.asyncio -async def test_proxy_matching_with_custom_proxy_headers(httpx_mock: HTTPXMock) -> None: +async def test_proxy_matching_with_custom_proxy_headers( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(proxy_url="http://my_test_proxy/") - async with httpx.AsyncClient( - proxy=httpx.Proxy("http://my_test_proxy", headers={"X-Something": "value"}) + async with backend.httpx.AsyncClient( + proxy=backend.httpx.Proxy( + "http://my_test_proxy", headers={"X-Something": "value"} + ) ) as client: response = await client.get("https://test_url") assert response.read() == b"" @@ -1598,11 +1766,12 @@ async def test_proxy_matching_with_custom_proxy_headers(httpx_mock: HTTPXMock) - @pytest.mark.asyncio async def test_proxy_matching_with_authentication_and_custom_proxy_headers( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(proxy_url="http://user:pwd@my_other_proxy/") - async with httpx.AsyncClient( - proxy=httpx.Proxy( + async with backend.httpx.AsyncClient( + proxy=backend.httpx.Proxy( "http://user:pwd@my_other_proxy", headers={"X-Something": "value"} ) ) as client: @@ -1612,11 +1781,11 @@ async def test_proxy_matching_with_authentication_and_custom_proxy_headers( @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_proxy_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_proxy_not_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: 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: + async with backend.httpx.AsyncClient(proxy="http://my_test_proxy") as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get("http://test_url") assert ( str(exception_info.value) @@ -1627,11 +1796,11 @@ async def test_proxy_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_proxy_not_existing(httpx_mock: HTTPXMock) -> None: +async def test_proxy_not_existing(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: 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: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get("http://test_url") assert ( str(exception_info.value) @@ -1641,10 +1810,12 @@ async def test_proxy_not_existing(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_requests_retrieval_content_matching(httpx_mock: HTTPXMock) -> None: +async def test_requests_retrieval_content_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.post("https://test_url", content=b"This is the body") await client.post("https://test_url2", content=b"This is the body") await client.post("https://test_url2", content=b"This is the body2") @@ -1653,10 +1824,12 @@ async def test_requests_retrieval_content_matching(httpx_mock: HTTPXMock) -> Non @pytest.mark.asyncio -async def test_requests_retrieval_json_matching(httpx_mock: HTTPXMock) -> None: +async def test_requests_retrieval_json_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.post("https://test_url", json=["my_str"]) await client.post("https://test_url2", json=["my_str"]) await client.post("https://test_url2", json=["my_str2"]) @@ -1665,13 +1838,15 @@ async def test_requests_retrieval_json_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_requests_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: +async def test_requests_retrieval_proxy_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient( + async with backend.httpx.AsyncClient( mounts={ - "http://": httpx.AsyncHTTPTransport(proxy="http://my_test_proxy"), - "https://": httpx.AsyncHTTPTransport( + "http://": backend.httpx.AsyncHTTPTransport(proxy="http://my_test_proxy"), + "https://": backend.httpx.AsyncHTTPTransport( proxy="http://user:pwd@my_other_proxy" ), } @@ -1686,13 +1861,15 @@ async def test_requests_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_request_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: +async def test_request_retrieval_proxy_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient( + async with backend.httpx.AsyncClient( mounts={ - "http://": httpx.AsyncHTTPTransport(proxy="http://my_test_proxy"), - "https://": httpx.AsyncHTTPTransport( + "http://": backend.httpx.AsyncHTTPTransport(proxy="http://my_test_proxy"), + "https://": backend.httpx.AsyncHTTPTransport( proxy="http://user:pwd@my_other_proxy" ), } @@ -1707,10 +1884,11 @@ async def test_request_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio async def test_requests_retrieval_files_and_data_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.put( "https://test_url", files={"name": ("file_name", b"File content")}, @@ -1739,10 +1917,12 @@ async def test_requests_retrieval_files_and_data_matching( @pytest.mark.asyncio -async def test_request_retrieval_files_and_data_matching(httpx_mock: HTTPXMock) -> None: +async def test_request_retrieval_files_and_data_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.put( "https://test_url", files={"name": ("file_name", b"File content")}, @@ -1758,10 +1938,12 @@ async def test_request_retrieval_files_and_data_matching(httpx_mock: HTTPXMock) @pytest.mark.asyncio -async def test_requests_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> None: +async def test_requests_retrieval_extensions_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.get("https://test_url") await client.get("https://test_url2", timeout=10) await client.get("https://test_url2", timeout=10) @@ -1778,11 +1960,13 @@ async def test_requests_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> @pytest.mark.asyncio -async def test_request_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> None: +async def test_request_retrieval_extensions_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: 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)) + async with backend.httpx.AsyncClient() as client: + await client.get("https://test_url", timeout=backend.httpx.Timeout(5, read=10)) await client.get("https://test_url2", timeout=10) await client.get("http://test_url2", timeout=10) @@ -1793,11 +1977,13 @@ async def test_request_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> N @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_content_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_content_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: 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: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body2") assert ( str(exception_info.value) @@ -1807,30 +1993,32 @@ async def test_content_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_json_matching(httpx_mock: HTTPXMock) -> None: +async def test_json_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(match_json={"a": 1, "b": 2}) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url", json={"b": 2, "a": 1}) assert response.read() == b"" @pytest.mark.asyncio -async def test_json_partial_matching(httpx_mock: HTTPXMock) -> None: +async def test_json_partial_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(match_json={"a": 1, "b": ANY}) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url", json={"b": 2, "a": 1}) assert response.read() == b"" @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_json_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_json_not_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: 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: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", json={"c": 3, "b": 2, "a": 1}) assert ( str(exception_info.value) @@ -1841,15 +2029,17 @@ async def test_json_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_headers_and_json_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_headers_and_json_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( match_json={"a": 1, "b": 2}, match_headers={"foo": "bar"}, is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", json={"c": 3, "b": 2, "a": 1}) assert ( str(exception_info.value) @@ -1860,11 +2050,13 @@ async def test_headers_and_json_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_match_json_invalid_json(httpx_mock: HTTPXMock) -> None: +async def test_match_json_invalid_json( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: 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: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"foobar") assert ( str(exception_info.value) @@ -1876,11 +2068,13 @@ async def test_match_json_invalid_json(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio @pytest.mark.parametrize("content", [b'{"a":"\xff"}', b'{"a":"\xc3"}']) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_match_json_invalid_utf8(httpx_mock: HTTPXMock, content: bytes) -> None: +async def test_match_json_invalid_utf8( + httpx_mock: HTTPXMock, backend: HTTPXBackend, content: bytes +) -> None: httpx_mock.add_response(match_json={"a": "value"}) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=content) assert exception_info.value.request.content == content assert "No response can be found" in str(exception_info.value) @@ -1890,11 +2084,13 @@ async def test_match_json_invalid_utf8(httpx_mock: HTTPXMock, content: bytes) -> @pytest.mark.asyncio -async def test_match_json_binary_content_fallback(httpx_mock: HTTPXMock) -> None: +async def test_match_json_binary_content_fallback( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(match_json={"a": "value"}, status_code=201) httpx_mock.add_response(match_content=b"\xff", status_code=202) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url", content=b"\xff") assert response.status_code == 202 response = await client.post("https://test_url", json={"a": "value"}) @@ -1903,11 +2099,11 @@ async def test_match_json_binary_content_fallback(httpx_mock: HTTPXMock) -> None @pytest.mark.asyncio async def test_requests_retrieval_json_matching_with_binary_content( - httpx_mock: HTTPXMock, + httpx_mock: HTTPXMock, backend: HTTPXBackend ) -> None: httpx_mock.add_response(is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: binary_request = ( await client.post("https://test_url", content=b"\xff") ).request @@ -1921,92 +2117,106 @@ async def test_requests_retrieval_json_matching_with_binary_content( @pytest.mark.asyncio -async def test_headers_and_content_matching(httpx_mock: HTTPXMock) -> None: +async def test_headers_and_content_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( - match_headers={"User-Agent": f"python-httpx/{httpx.__version__}"}, + match_headers={ + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}" + }, match_content=b"This is the body", ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url", content=b"This is the body") assert response.content == b"" @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_headers_not_matching_and_content_matching(httpx_mock: HTTPXMock) -> None: +async def test_headers_not_matching_and_content_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" ) @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_headers_matching_and_content_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_headers_matching_and_content_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url", }, match_content=b"This is the body2", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" ) @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_headers_and_content_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_headers_and_content_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body2", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" ) @pytest.mark.asyncio -async def test_url_and_headers_and_content_matching(httpx_mock: HTTPXMock) -> None: +async def test_url_and_headers_and_content_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", - match_headers={"User-Agent": f"python-httpx/{httpx.__version__}"}, + match_headers={ + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}" + }, match_content=b"This is the body", ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url", content=b"This is the body") assert response.content == b"" @@ -2015,24 +2225,25 @@ async def test_url_and_headers_and_content_matching(httpx_mock: HTTPXMock) -> No @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_headers_not_matching_and_url_and_content_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" ) @@ -2040,24 +2251,25 @@ async def test_headers_not_matching_and_url_and_content_matching( @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_and_headers_not_matching_and_content_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url2", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" ) @@ -2065,24 +2277,25 @@ async def test_url_and_headers_not_matching_and_content_matching( @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_and_headers_matching_and_content_not_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url", }, match_content=b"This is the body2", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" ) @@ -2090,24 +2303,25 @@ async def test_url_and_headers_matching_and_content_not_matching( @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_headers_matching_and_url_and_content_not_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url2", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url", }, match_content=b"This is the body2", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" ) @@ -2115,62 +2329,68 @@ async def test_headers_matching_and_url_and_content_not_matching( @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_url_matching_and_headers_and_content_not_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body2", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" ) @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_url_and_headers_and_content_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_url_and_headers_and_content_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url2", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body2", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" ) @pytest.mark.asyncio async def test_method_and_url_and_headers_and_content_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url", method="POST", - match_headers={"User-Agent": f"python-httpx/{httpx.__version__}"}, + match_headers={ + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}" + }, match_content=b"This is the body", ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.post("https://test_url", content=b"This is the body") assert response.content == b"" @@ -2179,25 +2399,26 @@ async def test_method_and_url_and_headers_and_content_matching( @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, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url", method="POST", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match POST request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match POST request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" ) @@ -2205,25 +2426,26 @@ async def test_headers_not_matching_and_method_and_url_and_content_matching( @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, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url2", method="POST", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match POST request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match POST request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" ) @@ -2231,25 +2453,26 @@ async def test_url_and_headers_not_matching_and_method_and_content_matching( @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, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url", method="POST", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url", }, match_content=b"This is the body2", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match POST request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match POST request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" ) @@ -2257,25 +2480,26 @@ async def test_method_and_url_and_headers_matching_and_content_not_matching( @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, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url2", method="POST", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url", }, match_content=b"This is the body2", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match POST request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match POST request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" ) @@ -2283,25 +2507,26 @@ async def test_method_and_headers_matching_and_url_and_content_not_matching( @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, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url", method="POST", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body2", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match POST request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match POST request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" ) @@ -2309,25 +2534,26 @@ async def test_method_and_url_matching_and_headers_and_content_not_matching( @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, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url2", method="POST", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body2", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match POST request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match POST request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" ) @@ -2335,126 +2561,149 @@ async def test_method_matching_and_url_and_headers_and_content_not_matching( @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, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url2", method="PUT", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body2", is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match PUT request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match PUT request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" ) @pytest.mark.asyncio -async def test_header_as_str_tuple_list(httpx_mock: HTTPXMock) -> None: +async def test_header_as_str_tuple_list( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( headers=[("set-cookie", "key=value"), ("set-cookie", "key2=value2")] ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert dict(response.cookies) == {"key": "value", "key2": "value2"} @pytest.mark.asyncio -async def test_header_as_bytes_tuple_list(httpx_mock: HTTPXMock) -> None: +async def test_header_as_bytes_tuple_list( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( headers=[(b"set-cookie", b"key=value"), (b"set-cookie", b"key2=value2")] ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert dict(response.cookies) == {"key": "value", "key2": "value2"} @pytest.mark.asyncio -async def test_header_as_bytes_dict(httpx_mock: HTTPXMock) -> None: +async def test_header_as_bytes_dict( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(headers={b"set-cookie": b"key=value"}) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert dict(response.cookies) == {"key": "value"} @pytest.mark.asyncio -async def test_header_as_httpx_headers(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(headers=httpx.Headers({"set-cookie": "key=value"})) +async def test_header_as_httpx_headers( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + httpx_mock.add_response(headers=backend.httpx.Headers({"set-cookie": "key=value"})) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert dict(response.cookies) == {"key": "value"} @pytest.mark.asyncio -async def test_elapsed_when_add_response(httpx_mock: HTTPXMock) -> None: +async def test_elapsed_when_add_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response() - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.elapsed is not None @pytest.mark.asyncio -async def test_elapsed_when_add_callback(httpx_mock: HTTPXMock) -> None: +async def test_elapsed_when_add_callback( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_callback( - callback=lambda req: httpx.Response(status_code=200, json={"foo": "bar"}) + callback=lambda req: backend.httpx.Response( + status_code=200, json={"foo": "bar"} + ) ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.elapsed is not None @pytest.mark.asyncio -async def test_elapsed_when_add_async_callback(httpx_mock: HTTPXMock) -> None: - async def custom_response(request: httpx.Request) -> httpx.Response: - return httpx.Response(status_code=200, json={"foo": "bar"}) +async def test_elapsed_when_add_async_callback( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + async def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json={"foo": "bar"}) httpx_mock.add_callback(custom_response) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.elapsed is not None @pytest.mark.asyncio -async def test_non_ascii_url_response(httpx_mock: HTTPXMock) -> None: +async def test_non_ascii_url_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url?query_type=数据") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url?query_type=数据") assert response.content == b"" @pytest.mark.asyncio -async def test_url_encoded_matching_response(httpx_mock: HTTPXMock) -> None: +async def test_url_encoded_matching_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url?a=%E6%95%B0%E6%8D%AE") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url?a=数据") assert response.content == b"" @pytest.mark.asyncio -async def test_reset_is_removing_requests(httpx_mock: HTTPXMock) -> None: +async def test_reset_is_removing_requests( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response() - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.get("https://test_url") assert len(httpx_mock.get_requests()) == 1 @@ -2464,14 +2713,14 @@ async def test_reset_is_removing_requests(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_mutating_json(httpx_mock: HTTPXMock) -> None: +async def test_mutating_json(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: mutating_json = {"content": "request 1"} httpx_mock.add_response(json=mutating_json) mutating_json["content"] = "request 2" httpx_mock.add_response(json=mutating_json) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.get("https://test_url") assert response.json() == {"content": "request 1"} @@ -2482,32 +2731,35 @@ async def test_mutating_json(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio async def test_streams_are_not_cascading_resulting_in_maximum_recursion( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(json={"abc": "def"}, is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: tasks = [client.get("https://test_url") for _ in range(950)] await asyncio.gather(*tasks) # No need to assert anything, this test case ensure that no error was raised by the gather @pytest.mark.asyncio -async def test_custom_transport(httpx_mock: HTTPXMock) -> None: - class CustomTransport(httpx.AsyncHTTPTransport): +async def test_custom_transport(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: + class CustomTransport(backend.httpx.AsyncHTTPTransport): # type: ignore[name-defined] def __init__(self, prefix: str, *args, **kwargs): super().__init__(*args, **kwargs) self.prefix = prefix async def handle_async_request( self, - request: httpx.Request, - ) -> httpx.Response: + request: "httpx.Request", + ) -> "httpx.Response": httpx_response = await super().handle_async_request(request) httpx_response.headers["x-prefix"] = self.prefix return httpx_response httpx_mock.add_response() - async with httpx.AsyncClient(transport=CustomTransport(prefix="test")) as client: + async with backend.httpx.AsyncClient( + transport=CustomTransport(prefix="test") + ) as client: response = await client.post("https://test_url", content=b"This is the body") assert response.read() == b"" assert response.headers["x-prefix"] == "test" @@ -2516,6 +2768,7 @@ async def handle_async_request( @pytest.mark.asyncio async def test_response_selection_content_matching_with_async_iterable( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(match_content=b"full content 1", content=b"matched 1") httpx_mock.add_response(match_content=b"full content 2", content=b"matched 2") @@ -2532,7 +2785,7 @@ async def stream_content_2() -> AsyncIterable[bytes]: yield b"content" yield b" 2" - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response_2 = await client.put("https://test_url", content=stream_content_2()) response_1 = await client.put("https://test_url", content=stream_content_1()) assert response_1.content == b"matched 1" @@ -2542,6 +2795,7 @@ async def stream_content_2() -> AsyncIterable[bytes]: @pytest.mark.asyncio async def test_request_selection_content_matching_with_async_iterable( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(match_content=b"full content 1") httpx_mock.add_response(match_content=b"full content 2") @@ -2558,7 +2812,7 @@ async def stream_content_2() -> AsyncIterable[bytes]: yield b"content" yield b" 2" - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: await client.put("https://test_url_2", content=stream_content_2()) await client.put("https://test_url_1", content=stream_content_1()) @@ -2572,10 +2826,10 @@ async def stream_content_2() -> AsyncIterable[bytes]: @pytest.mark.asyncio -async def test_files_matching(httpx_mock: HTTPXMock) -> None: +async def test_files_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(match_files={"name": ("file_name", b"File content")}) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.put( "https://test_url", files={"name": ("file_name", b"File content")} ) @@ -2583,13 +2837,15 @@ async def test_files_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_files_and_data_matching(httpx_mock: HTTPXMock) -> None: +async def test_files_and_data_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( match_files={"name": ("file_name", b"File content")}, match_data={"field": "value"}, ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.put( "https://test_url", files={"name": ("file_name", b"File content")}, @@ -2600,7 +2856,9 @@ async def test_files_and_data_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_files_not_matching_name(httpx_mock: HTTPXMock, monkeypatch) -> None: +async def test_files_not_matching_name( + httpx_mock: HTTPXMock, monkeypatch, backend: HTTPXBackend +) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( os, @@ -2612,8 +2870,8 @@ async def test_files_not_matching_name(httpx_mock: HTTPXMock, monkeypatch) -> No 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: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.put( "https://test_url", files={"name1": ("file_name", b"File content")} ) @@ -2626,7 +2884,9 @@ async def test_files_not_matching_name(httpx_mock: HTTPXMock, monkeypatch) -> No @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_files_not_matching_file_name(httpx_mock: HTTPXMock, monkeypatch) -> None: +async def test_files_not_matching_file_name( + httpx_mock: HTTPXMock, monkeypatch, backend: HTTPXBackend +) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( os, @@ -2638,8 +2898,8 @@ async def test_files_not_matching_file_name(httpx_mock: HTTPXMock, monkeypatch) 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: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.put( "https://test_url", files={"name": ("file_name1", b"File content")} ) @@ -2652,7 +2912,9 @@ async def test_files_not_matching_file_name(httpx_mock: HTTPXMock, monkeypatch) @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_files_not_matching_content(httpx_mock: HTTPXMock, monkeypatch) -> None: +async def test_files_not_matching_content( + httpx_mock: HTTPXMock, monkeypatch, backend: HTTPXBackend +) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( os, @@ -2664,8 +2926,8 @@ async def test_files_not_matching_content(httpx_mock: HTTPXMock, monkeypatch) -> 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: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.put( "https://test_url", files={"name": ("file_name", b"File content1")} ) @@ -2679,7 +2941,7 @@ async def test_files_not_matching_content(httpx_mock: HTTPXMock, monkeypatch) -> @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) async def test_files_matching_but_data_not_matching( - httpx_mock: HTTPXMock, monkeypatch + httpx_mock: HTTPXMock, monkeypatch, backend: HTTPXBackend ) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( @@ -2694,8 +2956,8 @@ async def test_files_matching_but_data_not_matching( is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.put( "https://test_url", files={"name": ("file_name", b"File content")} ) @@ -2707,28 +2969,30 @@ async def test_files_matching_but_data_not_matching( @pytest.mark.asyncio -async def test_timeout_matching(httpx_mock: HTTPXMock) -> None: +async def test_timeout_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( match_extensions={"timeout": {"connect": 5, "read": 5, "write": 10, "pool": 5}} ) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.put( - "https://test_url", timeout=httpx.Timeout(5, write=10) + "https://test_url", timeout=backend.httpx.Timeout(5, write=10) ) assert response.content == b"" @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_timeout_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_timeout_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( match_extensions={"timeout": {"connect": 5, "read": 5, "write": 10, "pool": 5}}, is_optional=True, ) - async with httpx.AsyncClient() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get("https://test_url", extensions={"test": "value"}) assert ( str(exception_info.value) @@ -2738,10 +3002,12 @@ async def test_timeout_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_extensions_matching(httpx_mock: HTTPXMock) -> None: +async def test_extensions_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(match_extensions={"test": "value"}) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response = await client.put( "https://test_url", extensions={"test": "value", "test2": "value2"} ) @@ -2750,11 +3016,13 @@ async def test_extensions_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -async def test_extensions_not_matching(httpx_mock: HTTPXMock) -> None: +async def test_extensions_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: 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: + async with backend.httpx.AsyncClient() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: await client.get("https://test_url", extensions={"test": "value2"}) assert ( str(exception_info.value) @@ -2764,23 +3032,27 @@ async def test_extensions_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio -async def test_optional_response_not_matched(httpx_mock: HTTPXMock) -> None: +async def test_optional_response_not_matched( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> 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: + async with backend.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: +async def test_optional_response_matched( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> 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: + async with backend.httpx.AsyncClient() as client: response1 = await client.get("https://test_url") response2 = await client.get("https://test_url2") assert response1.content == b"" @@ -2789,31 +3061,37 @@ async def test_optional_response_matched(httpx_mock: HTTPXMock) -> None: @pytest.mark.asyncio @pytest.mark.httpx_mock(assert_all_responses_were_requested=False) -async def test_mandatory_response_matched(httpx_mock: HTTPXMock) -> None: +async def test_mandatory_response_matched( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> 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: + async with backend.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: +async def test_multi_response_matched_once( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url", is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.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: +async def test_multi_response_matched_twice( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url", is_reusable=True) - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: response1 = await client.get("https://test_url") response2 = await client.get("https://test_url") assert response1.content == b"" diff --git a/tests/test_httpx_sync.py b/tests/test_httpx_sync.py index 9f837ca..6ec4de0 100644 --- a/tests/test_httpx_sync.py +++ b/tests/test_httpx_sync.py @@ -1,20 +1,25 @@ import os import re from collections.abc import Iterable -from typing import Any +from typing import TYPE_CHECKING, Any from unittest.mock import ANY -import httpx import pytest from pytest import Testdir import pytest_httpx -from pytest_httpx import HTTPXMock +from pytest_httpx import HTTPXBackend, HTTPXMock, get_backends + +if TYPE_CHECKING: + try: + import httpx + except ModuleNotFoundError: + import httpx2 as httpx @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_without_response(httpx_mock: HTTPXMock) -> None: - with pytest.raises(Exception) as exception_info, httpx.Client() as client: +def test_without_response(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: + with pytest.raises(Exception) as exception_info, backend.httpx.Client() as client: client.get("https://test_url") assert ( str(exception_info.value) @@ -22,29 +27,31 @@ def test_without_response(httpx_mock: HTTPXMock) -> None: ) -def test_default_response(httpx_mock: HTTPXMock) -> None: +def test_default_response(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response() - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.content == b"" assert response.status_code == 200 - assert response.headers == httpx.Headers({}) + assert response.headers == backend.httpx.Headers({}) assert response.http_version == "HTTP/1.1" -def test_url_matching(httpx_mock: HTTPXMock) -> None: +def test_url_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(url="https://test_url") - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.content == b"" -def test_url_matching_reusing_response(httpx_mock: HTTPXMock) -> None: +def test_url_matching_reusing_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url", is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.content == b"" @@ -52,10 +59,12 @@ def test_url_matching_reusing_response(httpx_mock: HTTPXMock) -> None: assert response.content == b"" -def test_url_query_string_matching(httpx_mock: HTTPXMock) -> None: +def test_url_query_string_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url?a=1&b=2", is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url?a=1&b=2") assert response.content == b"" @@ -66,7 +75,7 @@ def test_url_query_string_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.parametrize("sequence_type", [list, tuple]) def test_url_query_params_partial_matching( - httpx_mock: HTTPXMock, sequence_type: type + httpx_mock: HTTPXMock, backend: HTTPXBackend, sequence_type: type ) -> None: httpx_mock.add_response( url="https://test_url", @@ -80,7 +89,7 @@ def test_url_query_params_partial_matching( is_reusable=True, ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url?a=1&b=2&a=3&c=4&d=5&d=6&e=7") assert response.content == b"" @@ -89,29 +98,35 @@ def test_url_query_params_partial_matching( assert response.content == b"" -def test_url_as_pattern_ignoring_query_parameters(httpx_mock: HTTPXMock): +def test_url_as_pattern_ignoring_query_parameters( + httpx_mock: HTTPXMock, backend: HTTPXBackend +): httpx_mock.add_response(url=re.compile("https://test_url/something.*")) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url/something?a=1&b=2") assert response.content == b"" -def test_url_query_params_with_single_value_list(httpx_mock: HTTPXMock) -> None: +def test_url_query_params_with_single_value_list( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url", match_params={"a": ["1"]}) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url?a=1") assert response.content == b"" -def test_url_query_params_with_non_str_name(httpx_mock: HTTPXMock) -> None: +def test_url_query_params_with_non_str_name( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", match_params={1: "1"}, ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url?1=1") assert response.content == b"" @@ -146,15 +161,17 @@ def test_regex_url_and_match_params(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_url_query_params_not_matching(httpx_mock: HTTPXMock) -> None: +def test_url_query_params_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", match_params={"a": "1"}, is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url?a=2") assert ( str(exception_info.value) @@ -219,10 +236,11 @@ def test_match_params_with_non_str_values_and_params_provided_as_dict( httpx_mock: HTTPXMock, match_params: dict[str, Any], request_params: dict[str, Any], + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(url="https://test_url", match_params=match_params) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url", params=request_params) assert response.content == b"" @@ -267,20 +285,22 @@ def test_match_params_with_non_str_values_and_params_in_requested_url( httpx_mock: HTTPXMock, match_params: dict[str, Any], url: str, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(url="https://test_url", match_params=match_params) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get(url) assert response.content == b"" def test_url_matching_with_more_than_one_value_on_same_param( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(url="https://test_url?a=1&a=3", is_optional=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url", params={"a": [1, 3]}) assert response.content == b"" @@ -288,11 +308,12 @@ def test_url_matching_with_more_than_one_value_on_same_param( @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_not_matching_with_more_than_one_value_on_same_param_and_diff_value( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(url="https://test_url?a=2&a=3", is_optional=True) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get("https://test_url", params={"a": [1, 3]}) assert ( str(exception_info.value) @@ -304,11 +325,12 @@ def test_url_not_matching_with_more_than_one_value_on_same_param_and_diff_value( @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_not_matching_with_more_than_one_value_on_same_param_and_more_values( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(url="https://test_url?a=1&a=3", is_optional=True) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get("https://test_url", params={"a": [1, 3, 4]}) assert ( str(exception_info.value) @@ -320,11 +342,12 @@ def test_url_not_matching_with_more_than_one_value_on_same_param_and_more_values @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_not_matching_with_more_than_one_value_on_same_param_and_less_values( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(url="https://test_url?a=1&a=3&a=4", is_optional=True) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get("https://test_url", params={"a": [1, 3]}) assert ( str(exception_info.value) @@ -334,11 +357,11 @@ def test_url_not_matching_with_more_than_one_value_on_same_param_and_less_values @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_url_not_matching(httpx_mock: HTTPXMock) -> None: +def test_url_not_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(url="https://test_url", is_optional=True) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get("https://test_url2") assert ( str(exception_info.value) @@ -348,11 +371,13 @@ def test_url_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_url_query_string_not_matching(httpx_mock: HTTPXMock) -> None: +def test_url_query_string_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: 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: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: # Same parameter order matters as it corresponds to a list on server side client.get("https://test_url?a=2&a=1") assert ( @@ -362,10 +387,10 @@ def test_url_query_string_not_matching(httpx_mock: HTTPXMock) -> None: ) -def test_method_matching(httpx_mock: HTTPXMock) -> None: +def test_method_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(method="get", is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.content == b"" @@ -374,11 +399,11 @@ def test_method_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_method_not_matching(httpx_mock: HTTPXMock) -> None: +def test_method_not_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(method="get", is_optional=True) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url") assert ( str(exception_info.value) @@ -387,12 +412,12 @@ def test_method_not_matching(httpx_mock: HTTPXMock) -> None: ) -def test_reusing_one_response(httpx_mock: HTTPXMock) -> None: +def test_reusing_one_response(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( url="https://test_url", content=b"test content", is_reusable=True ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.content == b"test content" @@ -400,32 +425,38 @@ def test_reusing_one_response(httpx_mock: HTTPXMock) -> None: assert response.content == b"test content" -def test_response_with_string_body(httpx_mock: HTTPXMock) -> None: +def test_response_with_string_body( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url", text="test content") - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.content == b"test content" -def test_response_with_html_string_body(httpx_mock: HTTPXMock) -> None: +def test_response_with_html_string_body( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url", html="test content") - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.text == "test content" @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_url_not_matching_upper_case_headers_matching(httpx_mock: HTTPXMock) -> None: +def test_url_not_matching_upper_case_headers_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> 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: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get("https://test_url", headers={"MyHeader": "Something"}) assert ( str(exception_info.value) @@ -434,96 +465,102 @@ def test_url_not_matching_upper_case_headers_matching(httpx_mock: HTTPXMock) -> ) -def test_stream_response_streaming(httpx_mock: HTTPXMock) -> None: +def test_stream_response_streaming( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> 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: + with backend.httpx.Client() as client: with client.stream(method="GET", url="https://test_url") as response: assert list(response.iter_raw()) == [b"part 1", b"part 2"] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): list(response.iter_raw()) # Assert a response can be streamed more than once with client.stream(method="GET", url="https://test_url") as response: assert list(response.iter_raw()) == [b"part 1", b"part 2"] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): list(response.iter_raw()) -def test_content_response_streaming(httpx_mock: HTTPXMock) -> None: +def test_content_response_streaming( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", content=b"part 1 and 2", is_reusable=True, ) - with httpx.Client() as client: + with backend.httpx.Client() as client: with client.stream(method="GET", url="https://test_url") as response: assert list(response.iter_raw()) == [b"part 1 and 2"] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): list(response.iter_raw()) # Assert a response can be streamed more than once with client.stream(method="GET", url="https://test_url") as response: assert list(response.iter_raw()) == [b"part 1 and 2"] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): list(response.iter_raw()) -def test_text_response_streaming(httpx_mock: HTTPXMock) -> None: +def test_text_response_streaming(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( url="https://test_url", text="part 1 and 2", is_reusable=True, ) - with httpx.Client() as client: + with backend.httpx.Client() as client: with client.stream(method="GET", url="https://test_url") as response: assert list(response.iter_raw()) == [b"part 1 and 2"] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): list(response.iter_raw()) # Assert a response can be streamed more than once with client.stream(method="GET", url="https://test_url") as response: assert list(response.iter_raw()) == [b"part 1 and 2"] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): list(response.iter_raw()) -def test_default_response_streaming(httpx_mock: HTTPXMock) -> None: +def test_default_response_streaming( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: with client.stream(method="GET", url="https://test_url") as response: assert list(response.iter_raw()) == [] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): list(response.iter_raw()) # Assert a response can be streamed more than once with client.stream(method="GET", url="https://test_url") as response: assert list(response.iter_raw()) == [] # Assert that stream still behaves the proper way (can only be consumed once per request) - with pytest.raises(httpx.StreamConsumed): + with pytest.raises(backend.httpx.StreamConsumed): list(response.iter_raw()) -def test_with_many_responses(httpx_mock: HTTPXMock) -> None: +def test_with_many_responses(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> 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") - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.content == b"test content 1" @@ -534,13 +571,15 @@ def test_with_many_responses(httpx_mock: HTTPXMock) -> None: assert response.content == b"test content 2" -def test_with_many_reused_responses(httpx_mock: HTTPXMock) -> None: +def test_with_many_reused_responses( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> 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", is_reusable=True ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.content == b"test content 1" @@ -551,7 +590,9 @@ def test_with_many_reused_responses(httpx_mock: HTTPXMock) -> None: assert response.content == b"test content 2" -def test_with_many_responses_methods(httpx_mock: HTTPXMock) -> None: +def test_with_many_responses_methods( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", method="GET", content=b"test content 1" ) @@ -571,7 +612,7 @@ def test_with_many_responses_methods(httpx_mock: HTTPXMock) -> None: url="https://test_url", method="HEAD", content=b"test content 6" ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url") assert response.content == b"test content 2" @@ -591,7 +632,9 @@ def test_with_many_responses_methods(httpx_mock: HTTPXMock) -> None: assert response.content == b"test content 4" -def test_with_many_responses_status_codes(httpx_mock: HTTPXMock) -> None: +def test_with_many_responses_status_codes( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", method="GET", content=b"test content 1", status_code=200 ) @@ -623,7 +666,7 @@ def test_with_many_responses_status_codes(httpx_mock: HTTPXMock) -> None: status_code=500, ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url") assert response.content == b"test content 2" assert response.status_code == 201 @@ -649,7 +692,9 @@ def test_with_many_responses_status_codes(httpx_mock: HTTPXMock) -> None: assert response.status_code == 303 -def test_with_many_responses_urls_str(httpx_mock: HTTPXMock) -> None: +def test_with_many_responses_urls_str( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url?param1=test", method="GET", content=b"test content 1" ) @@ -669,35 +714,45 @@ def test_with_many_responses_urls_str(httpx_mock: HTTPXMock) -> None: url="https://test_url?param6=test", method="HEAD", content=b"test content 6" ) - with httpx.Client() as client: - response = client.post(httpx.URL("https://test_url", params={"param2": "test"})) + with backend.httpx.Client() as client: + response = client.post( + backend.httpx.URL("https://test_url", params={"param2": "test"}) + ) assert response.content == b"test content 2" - response = client.get(httpx.URL("https://test_url", params={"param1": "test"})) + response = client.get( + backend.httpx.URL("https://test_url", params={"param1": "test"}) + ) assert response.content == b"test content 1" - response = client.put(httpx.URL("https://test_url", params={"param3": "test"})) + response = client.put( + backend.httpx.URL("https://test_url", params={"param3": "test"}) + ) assert response.content == b"test content 3" - response = client.head(httpx.URL("https://test_url", params={"param6": "test"})) + response = client.head( + backend.httpx.URL("https://test_url", params={"param6": "test"}) + ) assert response.content == b"test content 6" response = client.patch( - httpx.URL("https://test_url", params={"param5": "test"}) + backend.httpx.URL("https://test_url", params={"param5": "test"}) ) assert response.content == b"test content 5" response = client.delete( - httpx.URL("https://test_url", params={"param4": "test"}) + backend.httpx.URL("https://test_url", params={"param4": "test"}) ) assert response.content == b"test content 4" -def test_response_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: +def test_response_with_pattern_in_url( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url=re.compile(".*test.*")) httpx_mock.add_response(url="https://unmatched", content=b"test content") - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://unmatched") assert response.content == b"test content" @@ -705,11 +760,13 @@ def test_response_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: assert response.content == b"" -def test_request_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: +def test_request_with_pattern_in_url( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url") httpx_mock.add_response(url="https://unmatched") - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://unmatched") client.get("https://test_url", headers={"X-Test": "1"}) @@ -718,12 +775,14 @@ def test_request_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: assert request.headers["x-test"] == "1" -def test_requests_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: +def test_requests_with_pattern_in_url( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url") httpx_mock.add_response(url="https://tests_url") httpx_mock.add_response(url="https://unmatched") - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://tests_url", headers={"X-Test": "1"}) client.get("https://unmatched", headers={"X-Test": "2"}) client.get("https://test_url") @@ -734,12 +793,14 @@ def test_requests_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: assert "x-test" not in requests[1].headers -def test_callback_with_pattern_in_url(httpx_mock: HTTPXMock) -> None: - def custom_response(request: httpx.Request) -> httpx.Response: - return httpx.Response(status_code=200, json={"url": str(request.url)}) +def test_callback_with_pattern_in_url( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json={"url": str(request.url)}) - def custom_response2(request: httpx.Request) -> httpx.Response: - return httpx.Response( + def custom_response2(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response( status_code=200, extensions={"http_version": b"HTTP/2.0"}, json={"url": str(request.url)}, @@ -748,7 +809,7 @@ def custom_response2(request: httpx.Request) -> httpx.Response: httpx_mock.add_callback(custom_response, url=re.compile(".*test.*")) httpx_mock.add_callback(custom_response2, url="https://unmatched") - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://unmatched") assert response.http_version == "HTTP/2.0" @@ -756,39 +817,41 @@ def custom_response2(request: httpx.Request) -> httpx.Response: assert response.http_version == "HTTP/1.1" -def test_with_many_responses_urls_instances(httpx_mock: HTTPXMock) -> None: +def test_with_many_responses_urls_instances( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( - url=httpx.URL("https://test_url", params={"param1": "test"}), + url=backend.httpx.URL("https://test_url", params={"param1": "test"}), method="GET", content=b"test content 1", ) httpx_mock.add_response( - url=httpx.URL("https://test_url", params={"param2": "test"}), + url=backend.httpx.URL("https://test_url", params={"param2": "test"}), method="POST", content=b"test content 2", ) httpx_mock.add_response( - url=httpx.URL("https://test_url", params={"param3": "test"}), + url=backend.httpx.URL("https://test_url", params={"param3": "test"}), method="PUT", content=b"test content 3", ) httpx_mock.add_response( - url=httpx.URL("https://test_url", params={"param4": "test"}), + url=backend.httpx.URL("https://test_url", params={"param4": "test"}), method="DELETE", content=b"test content 4", ) httpx_mock.add_response( - url=httpx.URL("https://test_url", params={"param5": "test"}), + url=backend.httpx.URL("https://test_url", params={"param5": "test"}), method="PATCH", content=b"test content 5", ) httpx_mock.add_response( - url=httpx.URL("https://test_url", params={"param6": "test"}), + url=backend.httpx.URL("https://test_url", params={"param6": "test"}), method="HEAD", content=b"test content 6", ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url?param2=test") assert response.content == b"test content 2" @@ -808,33 +871,33 @@ def test_with_many_responses_urls_instances(httpx_mock: HTTPXMock) -> None: assert response.content == b"test content 4" -def test_with_http_version_2(httpx_mock: HTTPXMock) -> None: +def test_with_http_version_2(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( url="https://test_url", http_version="HTTP/2", content=b"test content 1" ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.content == b"test content 1" assert response.http_version == "HTTP/2" -def test_with_headers(httpx_mock: HTTPXMock) -> None: +def test_with_headers(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( url="https://test_url", content=b"test content 1", headers={"X-Test": "Test value"}, ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.content == b"test content 1" - assert response.headers == httpx.Headers( + assert response.headers == backend.httpx.Headers( {"x-test": "Test value", "content-length": "14"} ) -def test_requests_retrieval(httpx_mock: HTTPXMock) -> None: +def test_requests_retrieval(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( url="https://test_url", method="GET", content=b"test content 1" ) @@ -854,7 +917,7 @@ def test_requests_retrieval(httpx_mock: HTTPXMock) -> None: url="https://test_url", method="HEAD", content=b"test content 6" ) - with httpx.Client() as client: + with backend.httpx.Client() as client: client.post("https://test_url", content=b"sent content 2") client.get("https://test_url", headers={"X-TEST": "test header 1"}) client.put("https://test_url", content=b"sent content 3") @@ -863,71 +926,77 @@ def test_requests_retrieval(httpx_mock: HTTPXMock) -> None: client.delete("https://test_url", headers={"X-Test": "test header 4"}) patch_request = httpx_mock.get_request( - url=httpx.URL("https://test_url"), method="PATCH" + url=backend.httpx.URL("https://test_url"), method="PATCH" ) assert patch_request is not None assert patch_request.read() == b"sent content 5" head_request = httpx_mock.get_request( - url=httpx.URL("https://test_url"), method="HEAD" + url=backend.httpx.URL("https://test_url"), method="HEAD" ) assert head_request is not None assert head_request.read() == b"" put_request = httpx_mock.get_request( - url=httpx.URL("https://test_url"), method="PUT" + url=backend.httpx.URL("https://test_url"), method="PUT" ) assert put_request is not None assert put_request.read() == b"sent content 3" get_request = httpx_mock.get_request( - url=httpx.URL("https://test_url"), method="GET" + url=backend.httpx.URL("https://test_url"), method="GET" ) assert get_request is not None assert get_request.headers["x-test"] == "test header 1" post_request = httpx_mock.get_request( - url=httpx.URL("https://test_url"), method="POST" + url=backend.httpx.URL("https://test_url"), method="POST" ) assert post_request is not None assert post_request.read() == b"sent content 2" delete_request = httpx_mock.get_request( - url=httpx.URL("https://test_url"), method="DELETE" + url=backend.httpx.URL("https://test_url"), method="DELETE" ) assert delete_request is not None assert delete_request.headers["x-test"] == "test header 4" -def test_requests_retrieval_on_same_url(httpx_mock: HTTPXMock) -> None: +def test_requests_retrieval_on_same_url( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url", is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://test_url", headers={"X-TEST": "test header 1"}) client.get("https://test_url", headers={"X-TEST": "test header 2"}) - requests = httpx_mock.get_requests(url=httpx.URL("https://test_url")) + requests = httpx_mock.get_requests(url=backend.httpx.URL("https://test_url")) assert len(requests) == 2 assert requests[0].headers["x-test"] == "test header 1" assert requests[1].headers["x-test"] == "test header 2" -def test_request_retrieval_on_same_url(httpx_mock: HTTPXMock) -> None: +def test_request_retrieval_on_same_url( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://test_url", headers={"X-TEST": "test header 1"}) client.get("https://test_url2", headers={"X-TEST": "test header 2"}) - request = httpx_mock.get_request(url=httpx.URL("https://test_url")) + request = httpx_mock.get_request(url=backend.httpx.URL("https://test_url")) assert request is not None assert request.headers["x-test"] == "test header 1" -def test_requests_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: +def test_requests_retrieval_on_same_method( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://test_url", headers={"X-TEST": "test header 1"}) client.get("https://test_url2", headers={"X-TEST": "test header 2"}) @@ -937,10 +1006,12 @@ def test_requests_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: assert requests[1].headers["x-test"] == "test header 2" -def test_request_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: +def test_request_retrieval_on_same_method( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://test_url", headers={"X-TEST": "test header 1"}) client.post("https://test_url", headers={"X-TEST": "test header 2"}) @@ -949,25 +1020,31 @@ def test_request_retrieval_on_same_method(httpx_mock: HTTPXMock) -> None: assert request.headers["x-test"] == "test header 1" -def test_requests_retrieval_on_same_url_and_method(httpx_mock: HTTPXMock) -> None: +def test_requests_retrieval_on_same_url_and_method( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://test_url", headers={"X-TEST": "test header 1"}) client.get("https://test_url", headers={"X-TEST": "test header 2"}) client.post("https://test_url", headers={"X-TEST": "test header 3"}) client.get("https://test_url2", headers={"X-TEST": "test header 4"}) - requests = httpx_mock.get_requests(url=httpx.URL("https://test_url"), method="GET") + requests = httpx_mock.get_requests( + url=backend.httpx.URL("https://test_url"), method="GET" + ) assert len(requests) == 2 assert requests[0].headers["x-test"] == "test header 1" assert requests[1].headers["x-test"] == "test header 2" -def test_default_requests_retrieval(httpx_mock: HTTPXMock) -> None: +def test_default_requests_retrieval( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: client.post("https://test_url", headers={"X-TEST": "test header 1"}) client.get("https://test_url2", headers={"X-TEST": "test header 2"}) @@ -977,10 +1054,12 @@ def test_default_requests_retrieval(httpx_mock: HTTPXMock) -> None: assert requests[1].headers["x-test"] == "test header 2" -def test_default_request_retrieval(httpx_mock: HTTPXMock) -> None: +def test_default_request_retrieval( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response() - with httpx.Client() as client: + with backend.httpx.Client() as client: client.post("https://test_url", headers={"X-TEST": "test header 1"}) request = httpx_mock.get_request() @@ -988,7 +1067,7 @@ def test_default_request_retrieval(httpx_mock: HTTPXMock) -> None: assert request.headers["x-test"] == "test header 1" -def test_requests_json_body(httpx_mock: HTTPXMock) -> None: +def test_requests_json_body(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( url="https://test_url", method="GET", json=["list content 1", "list content 2"] ) @@ -999,7 +1078,7 @@ def test_requests_json_body(httpx_mock: HTTPXMock) -> None: ) httpx_mock.add_response(url="https://test_url", method="PUT", json="string value") - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url") assert response.json() == {"key 1": "value 1", "key 2": "value 2"} assert response.headers["content-type"] == "application/json" @@ -1013,51 +1092,59 @@ def test_requests_json_body(httpx_mock: HTTPXMock) -> None: assert response.headers["content-type"] == "application/json" -def test_callback_raising_exception(httpx_mock: HTTPXMock) -> None: - def raise_timeout(request: httpx.Request) -> httpx.Response: - raise httpx.ReadTimeout( +def test_callback_raising_exception( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + def raise_timeout(request: "httpx.Request") -> "httpx.Response": + raise backend.httpx.ReadTimeout( f"Unable to read within {request.extensions['timeout']['read']}", request=request, ) httpx_mock.add_callback(raise_timeout, url="https://test_url") - with httpx.Client() as client: - with pytest.raises(httpx.ReadTimeout) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.ReadTimeout) as exception_info: client.get("https://test_url") assert str(exception_info.value) == "Unable to read within 5.0" -def test_request_exception_raising(httpx_mock: HTTPXMock) -> None: +def test_request_exception_raising( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_exception( - httpx.ReadTimeout("Unable to read within 5.0"), url="https://test_url" + backend.httpx.ReadTimeout("Unable to read within 5.0"), url="https://test_url" ) - with httpx.Client() as client: - with pytest.raises(httpx.ReadTimeout) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.ReadTimeout) as exception_info: client.get("https://test_url") assert str(exception_info.value) == "Unable to read within 5.0" assert exception_info.value.request is not None -def test_non_request_exception_raising(httpx_mock: HTTPXMock) -> None: +def test_non_request_exception_raising( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_exception( - httpx.HTTPError("Unable to read within 5.0"), url="https://test_url" + backend.httpx.HTTPError("Unable to read within 5.0"), url="https://test_url" ) - with httpx.Client() as client: - with pytest.raises(httpx.HTTPError) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.HTTPError) as exception_info: client.get("https://test_url") assert str(exception_info.value) == "Unable to read within 5.0" -def test_callback_returning_response(httpx_mock: HTTPXMock) -> None: - def custom_response(request: httpx.Request) -> httpx.Response: - return httpx.Response(status_code=200, json={"url": str(request.url)}) +def test_callback_returning_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json={"url": str(request.url)}) httpx_mock.add_callback(custom_response, url="https://test_url") - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.json() == {"url": "https://test_url"} assert response.headers["content-type"] == "application/json" @@ -1066,26 +1153,26 @@ def custom_response(request: httpx.Request) -> httpx.Response: @pytest.mark.parametrize("return_value", [None, "not a response"]) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_callback_not_returning_a_response( - httpx_mock: HTTPXMock, return_value: str | None + httpx_mock: HTTPXMock, return_value: str | None, backend: HTTPXBackend ) -> None: httpx_mock.add_callback(lambda request: return_value, url="https://test_url") - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get("https://test_url") assert ( str(exception_info.value) - == "Callback registered for GET request on https://test_url MUST return httpx.Response" + == f"Callback registered for GET request on https://test_url MUST return {backend.name}.Response" ) -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"]) +def test_callback_executed_twice(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: + def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json=["content"]) httpx_mock.add_callback(custom_response, is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.json() == ["content"] assert response.headers["content-type"] == "application/json" @@ -1095,14 +1182,16 @@ def custom_response(request: httpx.Request) -> httpx.Response: assert response.headers["content-type"] == "application/json" -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"]) +def test_callback_registered_after_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json=["content2"]) httpx_mock.add_response(json=["content1"]) httpx_mock.add_callback(custom_response, is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.json() == ["content1"] assert response.headers["content-type"] == "application/json" @@ -1117,14 +1206,16 @@ def custom_response(request: httpx.Request) -> httpx.Response: assert response.headers["content-type"] == "application/json" -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"]) +def test_response_registered_after_callback( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: + def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json=["content1"]) httpx_mock.add_callback(custom_response) httpx_mock.add_response(json=["content2"], is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.json() == ["content1"] assert response.headers["content-type"] == "application/json" @@ -1139,13 +1230,13 @@ def custom_response(request: httpx.Request) -> httpx.Response: assert response.headers["content-type"] == "application/json" -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"]) +def test_callback_matching_method(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: + def custom_response(request: "httpx.Request") -> "httpx.Response": + return backend.httpx.Response(status_code=200, json=["content"]) httpx_mock.add_callback(custom_response, method="GET", is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.json() == ["content"] assert response.headers["content-type"] == "application/json" @@ -1159,21 +1250,26 @@ def test_request_retrieval_with_more_than_one(testdir: Testdir) -> None: """ Single request cannot be returned if there is more than one matching. """ + testdir.makeconftest(""" + import pytest + from pytest_httpx import HTTPXBackend, get_backends + + @pytest.fixture(params=get_backends(), ids=lambda b: b.name) + def backend(request): + return request.param + """) testdir.makepyfile(""" - import httpx - - - def test_request_retrieval_with_more_than_one(httpx_mock): + def test_request_retrieval_with_more_than_one(httpx_mock, backend): httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://test_url", headers={"X-TEST": "test header 1"}) client.get("https://test_url", headers={"X-TEST": "test header 2"}) - httpx_mock.get_request(url=httpx.URL("https://test_url")) + httpx_mock.get_request(url=backend.httpx.URL("https://test_url")) """) result = testdir.runpytest() - result.assert_outcomes(failed=1) + result.assert_outcomes(failed=len(get_backends())) result.stdout.fnmatch_lines( [ "*AssertionError: More than one request (2) matched, use get_requests instead or refine your filters." @@ -1181,20 +1277,24 @@ def test_request_retrieval_with_more_than_one(httpx_mock): ) -def test_headers_matching(httpx_mock: HTTPXMock) -> None: +def test_headers_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( - match_headers={"User-Agent": f"python-httpx/{httpx.__version__}"} + match_headers={ + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}" + } ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.content == b"" -def test_multi_value_headers_matching(httpx_mock: HTTPXMock) -> None: +def test_multi_value_headers_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(match_headers={"my-custom-header": "value1, value2"}) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get( "https://test_url", headers=[("my-custom-header", "value1"), ("my-custom-header", "value2")], @@ -1205,13 +1305,14 @@ def test_multi_value_headers_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_multi_value_headers_not_matching_single_value_issued( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: 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: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get( "https://test_url", headers=[ @@ -1229,13 +1330,14 @@ def test_multi_value_headers_not_matching_single_value_issued( @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_multi_value_headers_not_matching_multi_value_issued( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: 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: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get( "https://test_url", headers=[ @@ -1251,64 +1353,74 @@ def test_multi_value_headers_not_matching_multi_value_issued( @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_headers_matching_respect_case(httpx_mock: HTTPXMock) -> None: +def test_headers_matching_respect_case( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( - match_headers={"user-agent": f"python-httpx/{httpx.__version__}"}, + match_headers={ + "user-agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}" + }, is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get("https://test_url") assert ( str(exception_info.value) - == f"""No response can be found for GET request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}'}} headers amongst: -- Match any request with {{'user-agent': 'python-httpx/{httpx.__version__}'}} headers""" + == f"""No response can be found for GET request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers amongst: +- Match any request with {{'user-agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers""" ) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_headers_not_matching(httpx_mock: HTTPXMock) -> None: +def test_headers_not_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", "Host2": "test_url", }, is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get("https://test_url") assert ( str(exception_info.value) - == f"""No response can be found for GET request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers amongst: -- Match any request with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2', 'Host2': 'test_url'}} headers""" + == f"""No response can be found for GET request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers amongst: +- Match any request with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2', 'Host2': 'test_url'}} headers""" ) -def test_content_matching(httpx_mock: HTTPXMock) -> None: +def test_content_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(match_content=b"This is the body") - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url", content=b"This is the body") assert response.read() == b"" -def test_proxy_matching_with_authentication(httpx_mock: HTTPXMock) -> None: +def test_proxy_matching_with_authentication( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(proxy_url="http://user:pwd@my_other_proxy/") - with httpx.Client(proxy="http://user:pwd@my_other_proxy") as client: + with backend.httpx.Client(proxy="http://user:pwd@my_other_proxy") as client: response = client.get("https://test_url") assert response.read() == b"" -def test_proxy_matching_with_custom_proxy_headers(httpx_mock: HTTPXMock) -> None: +def test_proxy_matching_with_custom_proxy_headers( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(proxy_url="http://my_test_proxy/") - with httpx.Client( - proxy=httpx.Proxy("http://my_test_proxy", headers={"X-Something": "value"}) + with backend.httpx.Client( + proxy=backend.httpx.Proxy( + "http://my_test_proxy", headers={"X-Something": "value"} + ) ) as client: response = client.get("https://test_url") assert response.read() == b"" @@ -1316,11 +1428,12 @@ def test_proxy_matching_with_custom_proxy_headers(httpx_mock: HTTPXMock) -> None def test_proxy_matching_with_authentication_and_custom_proxy_headers( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(proxy_url="http://user:pwd@my_other_proxy/") - with httpx.Client( - proxy=httpx.Proxy( + with backend.httpx.Client( + proxy=backend.httpx.Proxy( "http://user:pwd@my_other_proxy", headers={"X-Something": "value"} ) ) as client: @@ -1329,11 +1442,11 @@ def test_proxy_matching_with_authentication_and_custom_proxy_headers( @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_proxy_not_matching(httpx_mock: HTTPXMock) -> None: +def test_proxy_not_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: 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: + with backend.httpx.Client(proxy="http://my_test_proxy") as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get("http://test_url") assert ( str(exception_info.value) @@ -1343,11 +1456,11 @@ def test_proxy_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_proxy_not_existing(httpx_mock: HTTPXMock) -> None: +def test_proxy_not_existing(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: 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: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get("http://test_url") assert ( str(exception_info.value) @@ -1356,10 +1469,12 @@ def test_proxy_not_existing(httpx_mock: HTTPXMock) -> None: ) -def test_requests_retrieval_content_matching(httpx_mock: HTTPXMock) -> None: +def test_requests_retrieval_content_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: client.post("https://test_url", content=b"This is the body") client.post("https://test_url2", content=b"This is the body") client.post("https://test_url2", content=b"This is the body2") @@ -1367,10 +1482,12 @@ def test_requests_retrieval_content_matching(httpx_mock: HTTPXMock) -> None: assert len(httpx_mock.get_requests(match_content=b"This is the body")) == 2 -def test_requests_retrieval_json_matching(httpx_mock: HTTPXMock) -> None: +def test_requests_retrieval_json_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: client.post("https://test_url", json=["my_str"]) client.post("https://test_url2", json=["my_str"]) client.post("https://test_url2", json=["my_str2"]) @@ -1378,13 +1495,17 @@ def test_requests_retrieval_json_matching(httpx_mock: HTTPXMock) -> None: assert len(httpx_mock.get_requests(match_json=["my_str"])) == 2 -def test_requests_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: +def test_requests_retrieval_proxy_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client( + with backend.httpx.Client( mounts={ - "http://": httpx.HTTPTransport(proxy="http://my_test_proxy"), - "https://": httpx.HTTPTransport(proxy="http://user:pwd@my_other_proxy"), + "http://": backend.httpx.HTTPTransport(proxy="http://my_test_proxy"), + "https://": backend.httpx.HTTPTransport( + proxy="http://user:pwd@my_other_proxy" + ), } ) as client: client.get("https://test_url") @@ -1396,13 +1517,17 @@ def test_requests_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: ) -def test_request_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: +def test_request_retrieval_proxy_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client( + with backend.httpx.Client( mounts={ - "http://": httpx.HTTPTransport(proxy="http://my_test_proxy"), - "https://": httpx.HTTPTransport(proxy="http://user:pwd@my_other_proxy"), + "http://": backend.httpx.HTTPTransport(proxy="http://my_test_proxy"), + "https://": backend.httpx.HTTPTransport( + proxy="http://user:pwd@my_other_proxy" + ), } ) as client: client.get("https://test_url") @@ -1412,10 +1537,12 @@ def test_request_retrieval_proxy_matching(httpx_mock: HTTPXMock) -> None: assert httpx_mock.get_request(proxy_url="http://my_test_proxy/") -def test_requests_retrieval_files_and_data_matching(httpx_mock: HTTPXMock) -> None: +def test_requests_retrieval_files_and_data_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: client.put( "https://test_url", files={"name": ("file_name", b"File content")}, @@ -1443,10 +1570,12 @@ def test_requests_retrieval_files_and_data_matching(httpx_mock: HTTPXMock) -> No ) -def test_request_retrieval_files_and_data_matching(httpx_mock: HTTPXMock) -> None: +def test_request_retrieval_files_and_data_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: client.put( "https://test_url", files={"name": ("file_name", b"File content")}, @@ -1461,10 +1590,12 @@ def test_request_retrieval_files_and_data_matching(httpx_mock: HTTPXMock) -> Non ) -def test_requests_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> None: +def test_requests_retrieval_extensions_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://test_url") client.get("https://test_url2", timeout=10) client.get("https://test_url2", timeout=10) @@ -1480,11 +1611,13 @@ def test_requests_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> None: ) -def test_request_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> None: +def test_request_retrieval_extensions_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: - client.get("https://test_url", timeout=httpx.Timeout(5, read=10)) + with backend.httpx.Client() as client: + client.get("https://test_url", timeout=backend.httpx.Timeout(5, read=10)) client.get("https://test_url2", timeout=10) client.get("http://test_url2", timeout=10) @@ -1494,11 +1627,11 @@ def test_request_retrieval_extensions_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_content_not_matching(httpx_mock: HTTPXMock) -> None: +def test_content_not_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: 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: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body2") assert ( str(exception_info.value) @@ -1542,28 +1675,28 @@ def test_match_content_and_match_files_error(httpx_mock: HTTPXMock) -> None: ) -def test_json_matching(httpx_mock: HTTPXMock) -> None: +def test_json_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(match_json={"a": 1, "b": 2}) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url", json={"b": 2, "a": 1}) assert response.read() == b"" -def test_json_partial_matching(httpx_mock: HTTPXMock) -> None: +def test_json_partial_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(match_json={"a": 1, "b": ANY}) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url", json={"b": 2, "a": 1}) assert response.read() == b"" @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_json_not_matching(httpx_mock: HTTPXMock) -> None: +def test_json_not_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: 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: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", json={"c": 3, "b": 2, "a": 1}) assert ( str(exception_info.value) @@ -1573,15 +1706,17 @@ def test_json_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_headers_and_json_not_matching(httpx_mock: HTTPXMock) -> None: +def test_headers_and_json_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( match_json={"a": 1, "b": 2}, match_headers={"foo": "bar"}, is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", json={"c": 3, "b": 2, "a": 1}) assert ( str(exception_info.value) @@ -1591,11 +1726,11 @@ def test_headers_and_json_not_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_match_json_invalid_json(httpx_mock: HTTPXMock) -> None: +def test_match_json_invalid_json(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: 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: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"foobar") assert ( str(exception_info.value) @@ -1606,11 +1741,13 @@ def test_match_json_invalid_json(httpx_mock: HTTPXMock) -> None: @pytest.mark.parametrize("content", [b'{"a":"\xff"}', b'{"a":"\xc3"}']) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_match_json_invalid_utf8(httpx_mock: HTTPXMock, content: bytes) -> None: +def test_match_json_invalid_utf8( + httpx_mock: HTTPXMock, backend: HTTPXBackend, content: bytes +) -> None: httpx_mock.add_response(match_json={"a": "value"}) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=content) assert exception_info.value.request.content == content assert "No response can be found" in str(exception_info.value) @@ -1619,11 +1756,13 @@ def test_match_json_invalid_utf8(httpx_mock: HTTPXMock, content: bytes) -> None: assert response.status_code == 200 -def test_match_json_binary_content_fallback(httpx_mock: HTTPXMock) -> None: +def test_match_json_binary_content_fallback( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(match_json={"a": "value"}, status_code=201) httpx_mock.add_response(match_content=b"\xff", status_code=202) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url", content=b"\xff") assert response.status_code == 202 response = client.post("https://test_url", json={"a": "value"}) @@ -1631,11 +1770,11 @@ def test_match_json_binary_content_fallback(httpx_mock: HTTPXMock) -> None: def test_requests_retrieval_json_matching_with_binary_content( - httpx_mock: HTTPXMock, + httpx_mock: HTTPXMock, backend: HTTPXBackend ) -> None: httpx_mock.add_response(is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: binary_request = client.post("https://test_url", content=b"\xff").request json_request = client.post("https://test_url", json={"a": "value"}).request @@ -1644,88 +1783,102 @@ def test_requests_retrieval_json_matching_with_binary_content( assert httpx_mock.get_request(match_content=b"\xff") is binary_request -def test_headers_and_content_matching(httpx_mock: HTTPXMock) -> None: +def test_headers_and_content_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( - match_headers={"User-Agent": f"python-httpx/{httpx.__version__}"}, + match_headers={ + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}" + }, match_content=b"This is the body", ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url", content=b"This is the body") assert response.content == b"" @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_headers_not_matching_and_content_matching(httpx_mock: HTTPXMock) -> None: +def test_headers_not_matching_and_content_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" ) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_headers_matching_and_content_not_matching(httpx_mock: HTTPXMock) -> None: +def test_headers_matching_and_content_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url", }, match_content=b"This is the body2", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" ) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_headers_and_content_not_matching(httpx_mock: HTTPXMock) -> None: +def test_headers_and_content_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body2", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" ) -def test_url_and_headers_and_content_matching(httpx_mock: HTTPXMock) -> None: +def test_url_and_headers_and_content_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", - match_headers={"User-Agent": f"python-httpx/{httpx.__version__}"}, + match_headers={ + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}" + }, match_content=b"This is the body", ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url", content=b"This is the body") assert response.content == b"" @@ -1733,154 +1886,165 @@ def test_url_and_headers_and_content_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_headers_not_matching_and_url_and_content_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" ) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_and_headers_not_matching_and_content_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url2", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" ) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_and_headers_matching_and_content_not_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url", }, match_content=b"This is the body2", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" ) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_headers_matching_and_url_and_content_not_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url2", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url", }, match_content=b"This is the body2", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" ) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_url_matching_and_headers_and_content_not_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body2", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" ) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_url_and_headers_and_content_not_matching(httpx_mock: HTTPXMock) -> None: +def test_url_and_headers_and_content_not_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url2", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body2", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match any request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match any request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" ) -def test_method_and_url_and_headers_and_content_matching(httpx_mock: HTTPXMock) -> None: +def test_method_and_url_and_headers_and_content_matching( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( url="https://test_url", method="POST", - match_headers={"User-Agent": f"python-httpx/{httpx.__version__}"}, + match_headers={ + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}" + }, match_content=b"This is the body", ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.post("https://test_url", content=b"This is the body") assert response.content == b"" @@ -1888,255 +2052,274 @@ def test_method_and_url_and_headers_and_content_matching(httpx_mock: HTTPXMock) @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, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url", method="POST", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match POST request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match POST request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" ) @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, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url2", method="POST", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match POST request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match POST request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body' body""" ) @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, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url", method="POST", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url", }, match_content=b"This is the body2", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match POST request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match POST request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" ) @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, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url2", method="POST", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url", }, match_content=b"This is the body2", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match POST request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match POST request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url'}} headers and b'This is the body2' body""" ) @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, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url", method="POST", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body2", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match POST request on https://test_url with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match POST request on https://test_url with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" ) @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, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url2", method="POST", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body2", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match POST request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match POST request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" ) @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_method_and_url_and_headers_and_content_not_matching( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response( url="https://test_url2", method="PUT", match_headers={ - "User-Agent": f"python-httpx/{httpx.__version__}", + "User-Agent": f"python-{backend.httpx.__name__}/{backend.httpx.__version__}", "Host": "test_url2", }, match_content=b"This is the body2", is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.post("https://test_url", content=b"This is the body") assert ( str(exception_info.value) - == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-httpx/{httpx.__version__}'}} headers and b'This is the body' body amongst: -- Match PUT request on https://test_url2 with {{'User-Agent': 'python-httpx/{httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" + == f"""No response can be found for POST request on https://test_url with {{'Host': 'test_url', 'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}'}} headers and b'This is the body' body amongst: +- Match PUT request on https://test_url2 with {{'User-Agent': 'python-{backend.httpx.__name__}/{backend.httpx.__version__}', 'Host': 'test_url2'}} headers and b'This is the body2' body""" ) -def test_header_as_str_tuple_list(httpx_mock: HTTPXMock) -> None: +def test_header_as_str_tuple_list(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( headers=[("set-cookie", "key=value"), ("set-cookie", "key2=value2")] ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert dict(response.cookies) == {"key": "value", "key2": "value2"} -def test_header_as_bytes_tuple_list(httpx_mock: HTTPXMock) -> None: +def test_header_as_bytes_tuple_list( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response( headers=[(b"set-cookie", b"key=value"), (b"set-cookie", b"key2=value2")] ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert dict(response.cookies) == {"key": "value", "key2": "value2"} -def test_header_as_bytes_dict(httpx_mock: HTTPXMock) -> None: +def test_header_as_bytes_dict(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(headers={b"set-cookie": b"key=value"}) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert dict(response.cookies) == {"key": "value"} -def test_header_as_httpx_headers(httpx_mock: HTTPXMock) -> None: - httpx_mock.add_response(headers=httpx.Headers({"set-cookie": "key=value"})) +def test_header_as_httpx_headers(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: + httpx_mock.add_response(headers=backend.httpx.Headers({"set-cookie": "key=value"})) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert dict(response.cookies) == {"key": "value"} -def test_elapsed_when_add_response(httpx_mock: HTTPXMock) -> None: +def test_elapsed_when_add_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response() - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.elapsed is not None -def test_elapsed_when_add_callback(httpx_mock: HTTPXMock) -> None: +def test_elapsed_when_add_callback( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_callback( - callback=lambda req: httpx.Response(status_code=200, json={"foo": "bar"}) + callback=lambda req: backend.httpx.Response( + status_code=200, json={"foo": "bar"} + ) ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.elapsed is not None -def test_non_ascii_url_response(httpx_mock: HTTPXMock) -> None: +def test_non_ascii_url_response(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(url="https://test_url?query_type=数据") - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url?query_type=数据") assert response.content == b"" -def test_url_encoded_matching_response(httpx_mock: HTTPXMock) -> None: +def test_url_encoded_matching_response( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url?a=%E6%95%B0%E6%8D%AE") - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url?a=数据") assert response.content == b"" -def test_reset_is_removing_requests(httpx_mock: HTTPXMock) -> None: +def test_reset_is_removing_requests( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response() - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://test_url") assert len(httpx_mock.get_requests()) == 1 @@ -2145,14 +2328,14 @@ def test_reset_is_removing_requests(httpx_mock: HTTPXMock) -> None: assert len(httpx_mock.get_requests()) == 0 -def test_mutating_json(httpx_mock: HTTPXMock) -> None: +def test_mutating_json(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: mutating_json = {"content": "request 1"} httpx_mock.add_response(json=mutating_json) mutating_json["content"] = "request 2" httpx_mock.add_response(json=mutating_json) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.json() == {"content": "request 1"} @@ -2160,23 +2343,23 @@ def test_mutating_json(httpx_mock: HTTPXMock) -> None: assert response.json() == {"content": "request 2"} -def test_custom_transport(httpx_mock: HTTPXMock) -> None: - class CustomTransport(httpx.HTTPTransport): +def test_custom_transport(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: + class CustomTransport(backend.httpx.HTTPTransport): # type: ignore[name-defined] def __init__(self, prefix: str, *args, **kwargs): super().__init__(*args, **kwargs) self.prefix = prefix def handle_request( self, - request: httpx.Request, - ) -> httpx.Response: + request: "httpx.Request", + ) -> "httpx.Response": httpx_response = super().handle_request(request) httpx_response.headers["x-prefix"] = self.prefix return httpx_response httpx_mock.add_response() - with httpx.Client(transport=CustomTransport(prefix="test")) as client: + with backend.httpx.Client(transport=CustomTransport(prefix="test")) as client: response = client.post("https://test_url", content=b"This is the body") assert response.read() == b"" assert response.headers["x-prefix"] == "test" @@ -2184,6 +2367,7 @@ def handle_request( def test_response_selection_content_matching_with_iterable( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(match_content=b"full content 1", content=b"matched 1") httpx_mock.add_response(match_content=b"full content 2", content=b"matched 2") @@ -2200,7 +2384,7 @@ def stream_content_2() -> Iterable[bytes]: yield b"content" yield b" 2" - with httpx.Client() as client: + with backend.httpx.Client() as client: response_2 = client.put("https://test_url", content=stream_content_2()) response_1 = client.put("https://test_url", content=stream_content_1()) assert response_1.content == b"matched 1" @@ -2209,6 +2393,7 @@ def stream_content_2() -> Iterable[bytes]: def test_request_selection_content_matching_with_iterable( httpx_mock: HTTPXMock, + backend: HTTPXBackend, ) -> None: httpx_mock.add_response(match_content=b"full content 1") httpx_mock.add_response(match_content=b"full content 2") @@ -2225,7 +2410,7 @@ def stream_content_2() -> Iterable[bytes]: yield b"content" yield b" 2" - with httpx.Client() as client: + with backend.httpx.Client() as client: client.put("https://test_url_2", content=stream_content_2()) client.put("https://test_url_1", content=stream_content_1()) @@ -2238,23 +2423,23 @@ def stream_content_2() -> Iterable[bytes]: assert request2.url == "https://test_url_2" -def test_files_matching(httpx_mock: HTTPXMock) -> None: +def test_files_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(match_files={"name": ("file_name", b"File content")}) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.put( "https://test_url", files={"name": ("file_name", b"File content")} ) assert response.content == b"" -def test_files_and_data_matching(httpx_mock: HTTPXMock) -> None: +def test_files_and_data_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( match_files={"name": ("file_name", b"File content")}, match_data={"field": "value"}, ) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.put( "https://test_url", files={"name": ("file_name", b"File content")}, @@ -2264,7 +2449,9 @@ def test_files_and_data_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_files_not_matching_name(httpx_mock: HTTPXMock, monkeypatch) -> None: +def test_files_not_matching_name( + httpx_mock: HTTPXMock, monkeypatch, backend: HTTPXBackend +) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( os, @@ -2276,8 +2463,8 @@ def test_files_not_matching_name(httpx_mock: HTTPXMock, monkeypatch) -> None: match_files={"name2": ("file_name", b"File content")}, is_optional=True ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.put( "https://test_url", files={"name1": ("file_name", b"File content")} ) @@ -2289,7 +2476,9 @@ def test_files_not_matching_name(httpx_mock: HTTPXMock, monkeypatch) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_files_not_matching_file_name(httpx_mock: HTTPXMock, monkeypatch) -> None: +def test_files_not_matching_file_name( + httpx_mock: HTTPXMock, monkeypatch, backend: HTTPXBackend +) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( os, @@ -2301,8 +2490,8 @@ def test_files_not_matching_file_name(httpx_mock: HTTPXMock, monkeypatch) -> Non match_files={"name": ("file_name2", b"File content")}, is_optional=True ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.put( "https://test_url", files={"name": ("file_name1", b"File content")} ) @@ -2314,7 +2503,9 @@ def test_files_not_matching_file_name(httpx_mock: HTTPXMock, monkeypatch) -> Non @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_files_not_matching_content(httpx_mock: HTTPXMock, monkeypatch) -> None: +def test_files_not_matching_content( + httpx_mock: HTTPXMock, monkeypatch, backend: HTTPXBackend +) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( os, @@ -2326,8 +2517,8 @@ def test_files_not_matching_content(httpx_mock: HTTPXMock, monkeypatch) -> None: match_files={"name": ("file_name", b"File content2")}, is_optional=True ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.put( "https://test_url", files={"name": ("file_name", b"File content1")} ) @@ -2340,7 +2531,7 @@ def test_files_not_matching_content(httpx_mock: HTTPXMock, monkeypatch) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) def test_files_matching_but_data_not_matching( - httpx_mock: HTTPXMock, monkeypatch + httpx_mock: HTTPXMock, monkeypatch, backend: HTTPXBackend ) -> None: # Ensure generated boundary will be fbe495efe4cd41b941ca13e254d6b018 monkeypatch.setattr( @@ -2355,8 +2546,8 @@ def test_files_matching_but_data_not_matching( is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.put( "https://test_url", files={"name": ("file_name", b"File content")} ) @@ -2377,25 +2568,27 @@ def test_data_without_files(httpx_mock: HTTPXMock) -> None: ) -def test_timeout_matching(httpx_mock: HTTPXMock) -> None: +def test_timeout_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( match_extensions={"timeout": {"connect": 5, "read": 5, "write": 10, "pool": 5}} ) - with httpx.Client() as client: - response = client.put("https://test_url", timeout=httpx.Timeout(5, write=10)) + with backend.httpx.Client() as client: + response = client.put( + "https://test_url", timeout=backend.httpx.Timeout(5, write=10) + ) assert response.content == b"" @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_timeout_not_matching(httpx_mock: HTTPXMock) -> None: +def test_timeout_not_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response( match_extensions={"timeout": {"connect": 5, "read": 5, "write": 10, "pool": 5}}, is_optional=True, ) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get("https://test_url", extensions={"test": "value"}) assert ( str(exception_info.value) @@ -2404,10 +2597,10 @@ def test_timeout_not_matching(httpx_mock: HTTPXMock) -> None: ) -def test_extensions_matching(httpx_mock: HTTPXMock) -> None: +def test_extensions_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(match_extensions={"test": "value"}) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.put( "https://test_url", extensions={"test": "value", "test2": "value2"} ) @@ -2415,11 +2608,11 @@ def test_extensions_matching(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) -def test_extensions_not_matching(httpx_mock: HTTPXMock) -> None: +def test_extensions_not_matching(httpx_mock: HTTPXMock, backend: HTTPXBackend) -> None: httpx_mock.add_response(match_extensions={"test": "value"}, is_optional=True) - with httpx.Client() as client: - with pytest.raises(httpx.TimeoutException) as exception_info: + with backend.httpx.Client() as client: + with pytest.raises(backend.httpx.TimeoutException) as exception_info: client.get("https://test_url", extensions={"test": "value2"}) assert ( str(exception_info.value) @@ -2428,22 +2621,26 @@ def test_extensions_not_matching(httpx_mock: HTTPXMock) -> None: ) -def test_optional_response_not_matched(httpx_mock: HTTPXMock) -> None: +def test_optional_response_not_matched( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> 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: + with backend.httpx.Client() as client: response = client.get("https://test_url2") assert response.content == b"" -def test_optional_response_matched(httpx_mock: HTTPXMock) -> None: +def test_optional_response_matched( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> 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: + with backend.httpx.Client() as client: response1 = client.get("https://test_url") response2 = client.get("https://test_url2") assert response1.content == b"" @@ -2451,29 +2648,35 @@ def test_optional_response_matched(httpx_mock: HTTPXMock) -> None: @pytest.mark.httpx_mock(assert_all_responses_were_requested=False) -def test_mandatory_response_matched(httpx_mock: HTTPXMock) -> None: +def test_mandatory_response_matched( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> 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: + with backend.httpx.Client() as client: response = client.get("https://test_url2") assert response.content == b"" -def test_multi_response_matched_once(httpx_mock: HTTPXMock) -> None: +def test_multi_response_matched_once( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url", is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: response = client.get("https://test_url") assert response.content == b"" -def test_multi_response_matched_twice(httpx_mock: HTTPXMock) -> None: +def test_multi_response_matched_twice( + httpx_mock: HTTPXMock, backend: HTTPXBackend +) -> None: httpx_mock.add_response(url="https://test_url", is_reusable=True) - with httpx.Client() as client: + with backend.httpx.Client() as client: response1 = client.get("https://test_url") response2 = client.get("https://test_url") assert response1.content == b"" diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 13ed9ea..977536a 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -1,22 +1,26 @@ +import pytest from pytest import Testdir +from pytest_httpx import get_backends + +pytestmark = pytest.mark.usefixtures("plugin_tests_conftest") -def test_fixture_is_available(testdir: Testdir) -> None: - # create a temporary pytest test file - testdir.makepyfile(""" - import httpx +def backends_count() -> int: + return len(get_backends()) - def test_http(httpx_mock): + +def test_fixture_is_available(testdir: Testdir) -> None: + testdir.makepyfile(""" + def test_http(httpx_mock, backend): mock = httpx_mock.add_response(url="https://foo.tld") - r = httpx.get("https://foo.tld") + r = backend.httpx.get("https://foo.tld") assert httpx_mock.get_request() is not None """) - # run all tests with pytest result = testdir.runpytest() - result.assert_outcomes(passed=1) + result.assert_outcomes(passed=backends_count()) def test_httpx_mock_unused_response(testdir: Testdir) -> None: @@ -24,11 +28,11 @@ def test_httpx_mock_unused_response(testdir: Testdir) -> None: Unused responses should fail test case. """ testdir.makepyfile(""" - def test_httpx_mock_unused_response(httpx_mock): + def test_httpx_mock_unused_response(httpx_mock, backend): httpx_mock.add_response() """) result = testdir.runpytest() - result.assert_outcomes(errors=1, passed=1) + result.assert_outcomes(errors=backends_count(), passed=backends_count()) result.stdout.fnmatch_lines( [ "*AssertionError: The following responses are mocked but not requested:", @@ -49,11 +53,11 @@ def test_httpx_mock_unused_response_without_assertion(testdir: Testdir) -> None: import pytest @pytest.mark.httpx_mock(assert_all_responses_were_requested=False) - def test_httpx_mock_unused_response_without_assertion(httpx_mock): + def test_httpx_mock_unused_response_without_assertion(httpx_mock, backend): httpx_mock.add_response() """) result = testdir.runpytest() - result.assert_outcomes(passed=1) + result.assert_outcomes(passed=backends_count()) def test_httpx_mock_unused_callback(testdir: Testdir) -> None: @@ -61,7 +65,7 @@ def test_httpx_mock_unused_callback(testdir: Testdir) -> None: Unused callbacks should fail test case. """ testdir.makepyfile(""" - def test_httpx_mock_unused_callback(httpx_mock): + def test_httpx_mock_unused_callback(httpx_mock, backend): def unused(*args, **kwargs): pass @@ -69,7 +73,7 @@ def unused(*args, **kwargs): """) result = testdir.runpytest() - result.assert_outcomes(errors=1, passed=1) + result.assert_outcomes(errors=backends_count(), passed=backends_count()) result.stdout.fnmatch_lines( [ "*AssertionError: The following responses are mocked but not requested:", @@ -90,7 +94,7 @@ def test_httpx_mock_unused_callback_without_assertion(testdir: Testdir) -> None: import pytest @pytest.mark.httpx_mock(assert_all_responses_were_requested=False) - def test_httpx_mock_unused_callback_without_assertion(httpx_mock): + def test_httpx_mock_unused_callback_without_assertion(httpx_mock, backend): def unused(*args, **kwargs): pass @@ -98,7 +102,7 @@ def unused(*args, **kwargs): """) result = testdir.runpytest() - result.assert_outcomes(passed=1) + result.assert_outcomes(passed=backends_count()) def test_httpx_mock_unexpected_request(testdir: Testdir) -> None: @@ -107,17 +111,16 @@ def test_httpx_mock_unexpected_request(testdir: Testdir) -> None: assert_all_requests_were_expected option is set to True (default). """ testdir.makepyfile(""" - import httpx import pytest - def test_httpx_mock_unexpected_request(httpx_mock): - with httpx.Client() as client: + def test_httpx_mock_unexpected_request(httpx_mock, backend): + with backend.httpx.Client() as client: # Non mocked request - with pytest.raises(httpx.TimeoutException): + with pytest.raises(backend.httpx.TimeoutException): client.get("https://foo.tld") """) result = testdir.runpytest() - result.assert_outcomes(errors=1, passed=1) + result.assert_outcomes(errors=backends_count(), passed=backends_count()) result.stdout.fnmatch_lines( [ "*AssertionError: The following requests were not expected:", @@ -135,18 +138,17 @@ def test_httpx_mock_unexpected_request_without_assertion(testdir: Testdir) -> No assert_all_requests_were_expected option is set to False. """ testdir.makepyfile(""" - import httpx import pytest @pytest.mark.httpx_mock(assert_all_requests_were_expected=False) - def test_httpx_mock_unexpected_request(httpx_mock): - with httpx.Client() as client: + def test_httpx_mock_unexpected_request(httpx_mock, backend): + with backend.httpx.Client() as client: # Non mocked request - with pytest.raises(httpx.TimeoutException): + with pytest.raises(backend.httpx.TimeoutException): client.get("https://foo.tld") """) result = testdir.runpytest() - result.assert_outcomes(passed=1) + result.assert_outcomes(passed=backends_count()) def test_httpx_mock_already_matched_response(testdir: Testdir) -> None: @@ -155,19 +157,18 @@ def test_httpx_mock_already_matched_response(testdir: Testdir) -> None: can_send_already_matched_responses option is set to False (default). """ testdir.makepyfile(""" - import httpx import pytest - def test_httpx_mock_already_matched_response(httpx_mock): + def test_httpx_mock_already_matched_response(httpx_mock, backend): httpx_mock.add_response() - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://foo.tld") # Non mocked (already matched) request - with pytest.raises(httpx.TimeoutException): + with pytest.raises(backend.httpx.TimeoutException): client.get("https://foo.tld") """) result = testdir.runpytest() - result.assert_outcomes(errors=1, passed=1) + result.assert_outcomes(errors=backends_count(), passed=backends_count()) result.stdout.fnmatch_lines( [ "*AssertionError: The following requests were not expected:", @@ -185,41 +186,39 @@ def test_httpx_mock_reusing_matched_response(testdir: Testdir) -> None: can_send_already_matched_responses option is set to True. """ testdir.makepyfile(""" - import httpx import pytest @pytest.mark.httpx_mock(can_send_already_matched_responses=True) - def test_httpx_mock_reusing_matched_response(httpx_mock): + def test_httpx_mock_reusing_matched_response(httpx_mock, backend): httpx_mock.add_response() - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://foo.tld") # Reusing response client.get("https://foo.tld") """) result = testdir.runpytest() - result.assert_outcomes(passed=1) + result.assert_outcomes(passed=backends_count()) def test_httpx_mock_unmatched_request_without_responses( testdir: Testdir, ) -> None: testdir.makepyfile(""" - import httpx import pytest - def test_httpx_mock_unmatched_request_without_responses(httpx_mock): - with httpx.Client() as client: + def test_httpx_mock_unmatched_request_without_responses(httpx_mock, backend): + with backend.httpx.Client() as client: # This request will not be matched client.get("https://foo22.tld") # This code will not be reached client.get("https://foo3.tld") """) result = testdir.runpytest() - result.assert_outcomes(errors=1, failed=1) + result.assert_outcomes(errors=backends_count(), failed=backends_count()) # Assert the error that occurred - result.stdout.fnmatch_lines( + result.stdout.re_match_lines( [ - "*httpx.TimeoutException: No response can be found for GET request on https://foo22.tld", + r".*httpx2?\.TimeoutException: No response can be found for GET request on https://foo22\.tld", ], consecutive=True, ) @@ -239,40 +238,39 @@ def test_httpx_mock_unmatched_request_with_only_unmatched_responses( testdir: Testdir, ) -> None: testdir.makepyfile(""" - import httpx import pytest - def test_httpx_mock_unmatched_request_with_only_unmatched_responses(httpx_mock): + def test_httpx_mock_unmatched_request_with_only_unmatched_responses(httpx_mock, backend): # This response will not be sent (because of a typo in the URL) httpx_mock.add_response(url="https://foo2.tld") # This response will not be sent (because test execution failed earlier) httpx_mock.add_response(url="https://foo3.tld") - with httpx.Client() as client: + with backend.httpx.Client() as client: # This request will not be matched client.get("https://foo22.tld") # This code will not be reached client.get("https://foo3.tld") """) result = testdir.runpytest() - result.assert_outcomes(errors=1, failed=1) + result.assert_outcomes(errors=backends_count(), failed=backends_count()) # Assert the error that occurred - result.stdout.fnmatch_lines( + result.stdout.re_match_lines( [ - "*httpx.TimeoutException: No response can be found for GET request on https://foo22.tld amongst:", - "*- Match any request on https://foo2.tld", - "*- Match any request on https://foo3.tld", + r".*httpx2?\.TimeoutException: No response can be found for GET request on https://foo22\.tld amongst:", + r".*- Match any request on https://foo2\.tld", + r".*- Match any request on https://foo3\.tld", ], consecutive=True, ) # Assert the teardown assertion failure result.stdout.fnmatch_lines( [ - "*AssertionError: The following responses are mocked but not requested:", - "* - Match any request on https://foo2.tld", - "* - Match any request on https://foo3.tld", - "* ", - "* 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", + r"*AssertionError: The following responses are mocked but not requested:", + r"* - Match any request on https://foo2.tld", + r"* - Match any request on https://foo3.tld", + r"* ", + r"* 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, ) @@ -282,30 +280,29 @@ def test_httpx_mock_unmatched_request_with_only_unmatched_reusable_responses( testdir: Testdir, ) -> None: testdir.makepyfile(""" - import httpx import pytest @pytest.mark.httpx_mock(can_send_already_matched_responses=True) - def test_httpx_mock_unmatched_request_with_only_unmatched_responses(httpx_mock): + def test_httpx_mock_unmatched_request_with_only_unmatched_responses(httpx_mock, backend): # This response will not be sent (because of a typo in the URL) httpx_mock.add_response(url="https://foo2.tld", method="GET") # This response will not be sent (because test execution failed earlier) httpx_mock.add_response(url="https://foo3.tld") - with httpx.Client() as client: + with backend.httpx.Client() as client: # This request will not be matched client.get("https://foo22.tld") # This code will not be reached client.get("https://foo3.tld") """) result = testdir.runpytest() - result.assert_outcomes(errors=1, failed=1) + result.assert_outcomes(errors=backends_count(), failed=backends_count()) # Assert the error that occurred - result.stdout.fnmatch_lines( + result.stdout.re_match_lines( [ - "*httpx.TimeoutException: No response can be found for GET request on https://foo22.tld amongst:", - "*- Match GET request on https://foo2.tld", - "*- Match every request on https://foo3.tld", + r".*httpx2?\.TimeoutException: No response can be found for GET request on https://foo22\.tld amongst:", + r".*- Match GET request on https://foo2\.tld", + r".*- Match every request on https://foo3\.tld", ], consecutive=True, ) @@ -326,16 +323,15 @@ def test_httpx_mock_unmatched_request_with_only_matched_responses( testdir: Testdir, ) -> None: testdir.makepyfile(""" - import httpx import pytest - def test_httpx_mock_unmatched_request_with_only_matched_responses(httpx_mock): + def test_httpx_mock_unmatched_request_with_only_matched_responses(httpx_mock, backend): # Sent response httpx_mock.add_response(url="https://foo.tld") # Sent response httpx_mock.add_response(url="https://foo.tld") - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://foo.tld") client.get("https://foo.tld") # This request will not be matched @@ -344,15 +340,15 @@ def test_httpx_mock_unmatched_request_with_only_matched_responses(httpx_mock): client.get("https://foo3.tld") """) result = testdir.runpytest() - result.assert_outcomes(errors=1, failed=1) + result.assert_outcomes(errors=backends_count(), failed=backends_count()) # Assert the error that occurred - result.stdout.fnmatch_lines( + result.stdout.re_match_lines( [ - "*httpx.TimeoutException: No response can be found for GET request on https://foo22.tld amongst:", - "*- Already matched any request on https://foo.tld", - "*- Already matched any request on https://foo.tld", - "*", - "*If 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", + r".*httpx2?\.TimeoutException: No response can be found for GET request on https://foo22\.tld amongst:", + r".*- Already matched any request on https://foo\.tld", + r".*- Already matched any request on https://foo\.tld", + ".*", + r".*If 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", ], consecutive=True, ) @@ -372,17 +368,16 @@ def test_httpx_mock_unmatched_request_with_only_matched_reusable_responses( testdir: Testdir, ) -> None: testdir.makepyfile(""" - import httpx import pytest @pytest.mark.httpx_mock(can_send_already_matched_responses=True) - def test_httpx_mock_unmatched_request_with_only_matched_responses(httpx_mock): + def test_httpx_mock_unmatched_request_with_only_matched_responses(httpx_mock, backend): # Sent response httpx_mock.add_response(url="https://foo.tld") # Sent response httpx_mock.add_response(url="https://foo3.tld") - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://foo.tld") client.get("https://foo.tld") client.get("https://foo3.tld") @@ -392,13 +387,13 @@ def test_httpx_mock_unmatched_request_with_only_matched_responses(httpx_mock): client.get("https://foo3.tld") """) result = testdir.runpytest() - result.assert_outcomes(errors=1, failed=1) + result.assert_outcomes(errors=backends_count(), failed=backends_count()) # Assert the error that occurred - result.stdout.fnmatch_lines( + result.stdout.re_match_lines( [ - "*httpx.TimeoutException: No response can be found for GET request on https://foo22.tld amongst:", - "*- Match every request on https://foo.tld", - "*- Match every request on https://foo3.tld", + r".*httpx2?\.TimeoutException: No response can be found for GET request on https://foo22\.tld amongst:", + r".*- Match every request on https://foo\.tld", + r".*- Match every request on https://foo3\.tld", ], consecutive=True, ) @@ -418,10 +413,9 @@ def test_httpx_mock_unmatched_request_with_matched_and_unmatched_responses( testdir: Testdir, ) -> None: testdir.makepyfile(""" - import httpx import pytest - def test_httpx_mock_unmatched_request_with_matched_and_unmatched_responses(httpx_mock): + def test_httpx_mock_unmatched_request_with_matched_and_unmatched_responses(httpx_mock, backend): # Sent response httpx_mock.add_response(url="https://foo.tld") # This response will not be sent (because of a typo in the URL) @@ -431,7 +425,7 @@ def test_httpx_mock_unmatched_request_with_matched_and_unmatched_responses(httpx # This response will not be sent (because test execution failed earlier) httpx_mock.add_response(url="https://foo3.tld") - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://foo.tld") client.get("https://foo.tld") # This request will not be matched @@ -440,17 +434,17 @@ def test_httpx_mock_unmatched_request_with_matched_and_unmatched_responses(httpx client.get("https://foo3.tld") """) result = testdir.runpytest() - result.assert_outcomes(errors=1, failed=1) + result.assert_outcomes(errors=backends_count(), failed=backends_count()) # Assert the error that occurred - result.stdout.fnmatch_lines( + result.stdout.re_match_lines( [ - "*httpx.TimeoutException: No response can be found for GET request on https://foo22.tld amongst:", - "*- Match any request on https://foo2.tld", - "*- Match any request on https://foo3.tld", - "*- Already matched any request on https://foo.tld", - "*- Already matched any request on https://foo.tld", - "*", - "*If 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", + r".*httpx2?\.TimeoutException: No response can be found for GET request on https://foo22\.tld amongst:", + r".*- Match any request on https://foo2\.tld", + r".*- Match any request on https://foo3\.tld", + r".*- Already matched any request on https://foo\.tld", + r".*- Already matched any request on https://foo\.tld", + ".*", + r".*If 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", ], consecutive=True, ) @@ -471,11 +465,10 @@ def test_httpx_mock_unmatched_request_with_matched_and_unmatched_reusable_respon testdir: Testdir, ) -> None: testdir.makepyfile(""" - import httpx import pytest @pytest.mark.httpx_mock(can_send_already_matched_responses=True) - def test_httpx_mock_unmatched_request_with_matched_and_unmatched_responses(httpx_mock): + def test_httpx_mock_unmatched_request_with_matched_and_unmatched_responses(httpx_mock, backend): # Sent response httpx_mock.add_response(url="https://foo.tld") # This response will not be sent (because of a typo in the URL) @@ -485,7 +478,7 @@ def test_httpx_mock_unmatched_request_with_matched_and_unmatched_responses(httpx # This response will not be sent (because test execution failed earlier) httpx_mock.add_response(url="https://foo4.tld") - with httpx.Client() as client: + with backend.httpx.Client() as client: client.get("https://foo.tld") client.get("https://foo2.tld") client.get("https://foo.tld") @@ -495,15 +488,15 @@ def test_httpx_mock_unmatched_request_with_matched_and_unmatched_responses(httpx client.get("https://foo2.tld") """) result = testdir.runpytest() - result.assert_outcomes(errors=1, failed=1) + result.assert_outcomes(errors=backends_count(), failed=backends_count()) # Assert the error that occurred - result.stdout.fnmatch_lines( + result.stdout.re_match_lines( [ - "*httpx.TimeoutException: No response can be found for GET request on https://foo3.tld amongst:", - "*- Match every request on https://foo33.tld", - "*- Match every request on https://foo4.tld", - "*- Match every request on https://foo.tld", - "*- Match every request on https://foo2.tld", + r".*httpx2?.TimeoutException: No response can be found for GET request on https://foo3\.tld amongst:", + r".*- Match every request on https://foo33\.tld", + r".*- Match every request on https://foo4\.tld", + r".*- Match every request on https://foo\.tld", + r".*- Match every request on https://foo2\.tld", ], consecutive=True, ) @@ -525,19 +518,18 @@ def test_httpx_mock_should_mock_sync(testdir: Testdir) -> None: Non mocked requests should go through while other requests should be mocked. """ testdir.makepyfile(""" - import httpx import pytest @pytest.mark.httpx_mock(should_mock=lambda request: request.url.host != "localhost") - def test_httpx_mock_should_mock_sync(httpx_mock): + def test_httpx_mock_should_mock_sync(httpx_mock, backend): httpx_mock.add_response() - with httpx.Client() as client: + with backend.httpx.Client() as client: # Mocked request client.get("https://foo.tld") # Non mocked request - with pytest.raises(httpx.ConnectError): + with pytest.raises(backend.httpx.ConnectError): client.get("https://localhost:5005") # Assert that a single request was mocked @@ -545,7 +537,7 @@ def test_httpx_mock_should_mock_sync(httpx_mock): """) result = testdir.runpytest() - result.assert_outcomes(passed=1) + result.assert_outcomes(passed=backends_count()) def test_httpx_mock_should_mock_async(testdir: Testdir) -> None: @@ -553,20 +545,19 @@ def test_httpx_mock_should_mock_async(testdir: Testdir) -> None: Non mocked requests should go through while other requests should be mocked. """ testdir.makepyfile(""" - import httpx import pytest @pytest.mark.asyncio @pytest.mark.httpx_mock(should_mock=lambda request: request.url.host != "localhost") - async def test_httpx_mock_should_mock_async(httpx_mock): + async def test_httpx_mock_should_mock_async(httpx_mock, backend): httpx_mock.add_response() - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: # Mocked request await client.get("https://foo.tld") # Non mocked request - with pytest.raises(httpx.ConnectError): + with pytest.raises(backend.httpx.ConnectError): await client.get("https://localhost:5005") # Assert that a single request was mocked @@ -574,7 +565,7 @@ async def test_httpx_mock_should_mock_async(httpx_mock): """) result = testdir.runpytest() - result.assert_outcomes(passed=1) + result.assert_outcomes(passed=backends_count()) def test_httpx_mock_options_on_multi_levels_are_aggregated(testdir: Testdir) -> None: @@ -587,37 +578,40 @@ def test_httpx_mock_options_on_multi_levels_are_aggregated(testdir: Testdir) -> """ testdir.makeconftest(""" import pytest + from pytest_httpx import get_backends + @pytest.fixture(params=get_backends(), ids=lambda b: b.name) + def backend(request): + return request.param def pytest_collection_modifyitems(session, config, items): for item in items: item.add_marker(pytest.mark.httpx_mock(assert_all_responses_were_requested=False)) """) testdir.makepyfile(""" - import httpx import pytest pytestmark = pytest.mark.httpx_mock(assert_all_requests_were_expected=False, should_mock=lambda request: request.url.host != "https://foo.tld") @pytest.mark.asyncio @pytest.mark.httpx_mock(should_mock=lambda request: request.url.host != "localhost") - async def test_httpx_mock_options_on_multi_levels_are_aggregated(httpx_mock): + async def test_httpx_mock_options_on_multi_levels_are_aggregated(httpx_mock, backend): httpx_mock.add_response(url="https://foo.tld", headers={"x-pytest-httpx": "this was mocked"}) # This response will never be used, testing that assert_all_responses_were_requested is handled httpx_mock.add_response(url="https://never_called.url") - async with httpx.AsyncClient() as client: + async with backend.httpx.AsyncClient() as client: # Assert that previously set should_mock was overridden response = await client.get("https://foo.tld") assert response.headers["x-pytest-httpx"] == "this was mocked" # Assert that latest should_mock is handled - with pytest.raises(httpx.ConnectError): + with pytest.raises(backend.httpx.ConnectError): await client.get("https://localhost:5005") # Assert that assert_all_requests_were_expected is the one at module level - with pytest.raises(httpx.TimeoutException): + with pytest.raises(backend.httpx.TimeoutException): await client.get("https://unexpected.url") # Assert that 2 requests out of 3 were mocked @@ -625,7 +619,7 @@ async def test_httpx_mock_options_on_multi_levels_are_aggregated(httpx_mock): """) result = testdir.runpytest() - result.assert_outcomes(passed=1) + result.assert_outcomes(passed=backends_count()) def test_invalid_marker(testdir: Testdir) -> None: @@ -636,12 +630,12 @@ def test_invalid_marker(testdir: Testdir) -> None: import pytest @pytest.mark.httpx_mock(foo=123) - def test_invalid_marker(httpx_mock): + def test_invalid_marker(httpx_mock, backend): pass """) result = testdir.runpytest() - result.assert_outcomes(errors=1) + result.assert_outcomes(errors=backends_count()) result.stdout.re_match_lines([r".*got an unexpected keyword argument 'foo'"]) @@ -650,11 +644,10 @@ 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): + def test_mandatory_response_not_matched(httpx_mock, backend): # 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 @@ -662,7 +655,7 @@ def test_mandatory_response_not_matched(httpx_mock): """) result = testdir.runpytest() - result.assert_outcomes(errors=1, passed=1) + result.assert_outcomes(errors=backends_count(), passed=backends_count()) # Assert the teardown assertion failure result.stdout.fnmatch_lines( [ @@ -677,14 +670,12 @@ def test_mandatory_response_not_matched(httpx_mock): def test_reusable_response_not_matched(testdir: Testdir) -> None: testdir.makepyfile(""" - import httpx - - def test_reusable_response_not_matched(httpx_mock): + def test_reusable_response_not_matched(httpx_mock, backend): httpx_mock.add_response(url="https://test_url2", is_reusable=True) """) result = testdir.runpytest() - result.assert_outcomes(errors=1, passed=1) + result.assert_outcomes(errors=backends_count(), passed=backends_count()) # Assert the teardown assertion failure result.stdout.fnmatch_lines( [ diff --git a/tox.toml b/tox.toml new file mode 100644 index 0000000..48640cd --- /dev/null +++ b/tox.toml @@ -0,0 +1,18 @@ +env_list = ["clean", "httpx", "httpx2", "both"] + +[env_run_base] +# Editable mode so that coverage measures the repo source. +package = "editable" +extras = ["testing", "{env_name}"] +commands = [ + ["pytest", "--cov-append", { replace = "posargs", extend = true }], +] + +[env.clean] +skip_install = true +deps = ["coverage[toml]"] +commands = [["coverage", "erase"]] + +[env.both] +# The "both" environment needs both extras, so override the {env_name}-derived one. +extras = ["testing", "httpx", "httpx2"]