diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f9f53a..dcbbf35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.31.2] - 2024-09-23 +### Fixed +- `httpx_mock` marker can now be defined at different levels for a single test. + ## [0.31.1] - 2024-09-22 ### Fixed - It is now possible to match on content provided as async iterable by the client. @@ -341,7 +345,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - First release, should be considered as unstable for now as design might change. -[Unreleased]: https://github.com/Colin-b/pytest_httpx/compare/v0.31.1...HEAD +[Unreleased]: https://github.com/Colin-b/pytest_httpx/compare/v0.31.2...HEAD +[0.31.2]: https://github.com/Colin-b/pytest_httpx/compare/v0.31.1...v0.31.2 [0.31.1]: https://github.com/Colin-b/pytest_httpx/compare/v0.31.0...v0.31.1 [0.31.0]: https://github.com/Colin-b/pytest_httpx/compare/v0.30.0...v0.31.0 [0.30.0]: https://github.com/Colin-b/pytest_httpx/compare/v0.29.0...v0.30.0 diff --git a/README.md b/README.md index f28217b..13d957a 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Build status Coverage Code style: black -Number of tests +Number of tests Number of downloads

diff --git a/pytest_httpx/__init__.py b/pytest_httpx/__init__.py index 28f400d..402dd54 100644 --- a/pytest_httpx/__init__.py +++ b/pytest_httpx/__init__.py @@ -1,4 +1,5 @@ from collections.abc import Generator +from operator import methodcaller import httpx import pytest @@ -20,8 +21,11 @@ def httpx_mock( monkeypatch: MonkeyPatch, request: FixtureRequest, ) -> Generator[HTTPXMock, None, None]: - marker = request.node.get_closest_marker("httpx_mock") - options = HTTPXMockOptions.from_marker(marker) if marker else HTTPXMockOptions() + options = {} + for marker in request.node.iter_markers("httpx_mock"): + options = marker.kwargs | options + __tracebackhide__ = methodcaller("errisinstance", TypeError) + options = HTTPXMockOptions(**options) mock = HTTPXMock() diff --git a/pytest_httpx/_httpx_mock.py b/pytest_httpx/_httpx_mock.py index 9465418..3379e2b 100644 --- a/pytest_httpx/_httpx_mock.py +++ b/pytest_httpx/_httpx_mock.py @@ -1,11 +1,9 @@ import copy import inspect -from operator import methodcaller -from typing import Union, Optional, Callable, Any, NoReturn, AsyncIterable -from collections.abc import Awaitable, Iterable +from typing import Union, Optional, Callable, Any, NoReturn +from collections.abc import Awaitable import httpx -from pytest import Mark from pytest_httpx import _httpx_internals from pytest_httpx._pretty_print import RequestDescription @@ -32,12 +30,6 @@ def __init__( ] self.non_mocked_hosts = [*non_mocked_hosts, *missing_www] - @classmethod - def from_marker(cls, marker: Mark) -> "HTTPXMockOptions": - """Initialise from a marker so that the marker kwargs raise an error if incorrect.""" - __tracebackhide__ = methodcaller("errisinstance", TypeError) - return cls(**marker.kwargs) - class HTTPXMock: def __init__(self) -> None: diff --git a/pytest_httpx/version.py b/pytest_httpx/version.py index 91963a7..4cf9fbf 100644 --- a/pytest_httpx/version.py +++ b/pytest_httpx/version.py @@ -3,4 +3,4 @@ # Major should be incremented in case there is a breaking change. (eg: 2.5.8 -> 3.0.0) # Minor should be incremented in case there is an enhancement. (eg: 2.5.8 -> 2.6.0) # Patch should be incremented in case there is a bug fix. (eg: 2.5.8 -> 2.5.9) -__version__ = "0.31.1" +__version__ = "0.31.2" diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 2e4880d..a3149a0 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -215,6 +215,61 @@ async def test_httpx_mock_non_mocked_hosts_async(httpx_mock): result.assert_outcomes(passed=1) +def test_httpx_mock_options_on_multi_levels_are_aggregated(testdir: Testdir) -> None: + """ + Test case ensures that every level provides one parameter that should be used in the end + + global (actually registered AFTER module): assert_all_responses_were_requested (tested by putting unused response) + module: assert_all_requests_were_expected (tested by not mocking one URL) + test: non_mocked_hosts (tested by calling 3 URls, 2 mocked, the other one not) + """ + testdir.makeconftest( + """ + import pytest + + + 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, non_mocked_hosts=["https://foo.tld"]) + + @pytest.mark.asyncio + @pytest.mark.httpx_mock(non_mocked_hosts=["localhost"]) + async def test_httpx_mock_non_mocked_hosts_async(httpx_mock): + 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: + # Assert that previously set non_mocked_hosts was overridden + response = await client.get("https://foo.tld") + assert response.headers["x-pytest-httpx"] == "this was mocked" + + # Assert that latest non_mocked_hosts is handled + with pytest.raises(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): + await client.get("https://unexpected.url") + + # Assert that 2 requests out of 3 were mocked + assert len(httpx_mock.get_requests()) == 2 + + """ + ) + result = testdir.runpytest() + result.assert_outcomes(passed=1) + + def test_invalid_marker(testdir: Testdir) -> None: """ Unknown marker keyword arguments should raise a TypeError.