RESPX-like HTTP mocking for niquests
Project description
niquests-mock
RESPX-style HTTP mocking for niquests.
Installation
uv add niquests-mock
or
pip install niquests-mock
Usage
Fixture Style
This is the closest workflow to respx_mock in pytest.
import niquests
def test_fixture_style(niquests_mock):
route = niquests_mock.get("https://example.org/")
route.respond(status_code=200)
response = niquests.get("https://example.org/")
assert route.called
assert response.status_code == 200
The plugin also exposes respx_mock as a compatibility alias for easier migration from respx.
Development
uv sync --dev
just check
Decorator Style
Useful when you want a familiar respx-style test shape.
import niquests
import niquests_mock as nmock
@nmock.mock
def test_decorator_style():
route = nmock.get("https://example.org/", name="homepage").respond(status_code=200)
response = niquests.get("https://example.org/")
route.assert_called_once()
assert nmock.lookup("homepage") is route
assert response.status_code == 200
Decorator factory arguments work too:
import niquests
import niquests_mock as nmock
@nmock.mock(assert_all_called=True, base_url="https://api.example.test")
def test_strict_routes():
nmock.get("/health", name="health").respond(status_code=200)
response = niquests.get("https://api.example.test/health")
assert response.status_code == 200
Decorator calls use a fresh router for every decorated function invocation. The
fresh router copies router configuration such as assert_all_mocked,
assert_all_called, and base_url, but it does not reuse routes registered on
the decorator object itself. Register routes inside the decorated function via
nmock.get(...), nmock.route(...), or other top-level helpers.
Context Manager Style
Best when you want explicit router lifetime inside the test body.
import niquests
from niquests_mock import MockRouter
def test_context_manager():
with MockRouter(base_url="https://api.example.test") as router:
users = router.get("/users", name="users.list").respond(
status_code=200,
json=[{"id": 1, "name": "Ada"}],
)
response = niquests.get("https://api.example.test/users")
assert router["users.list"] is users
users.assert_called_once()
assert response.json() == [{"id": 1, "name": "Ada"}]
MockRouter can be nested. The innermost active router handles requests while it
is active, and the outer router is restored after the inner context exits. Patch
cleanup runs when a context exits, including when the test body raises an
exception. Repeated start() / stop() calls on the same router are idempotent.
Strict Mode and Pass-through
By default, assert_all_mocked=True: unmatched requests raise NoMockAddress.
Set assert_all_mocked=False to allow unmatched requests to use the original
niquests transport.
A route can opt into pass-through even in strict mode. The request then uses the
original niquests transport, so use this only for URLs that your test
environment intentionally allows:
with MockRouter(assert_all_mocked=True) as router:
router.get(live_url).pass_through()
response = niquests.get(live_url)
Use assert_all_called=True when every registered route must be exercised. On a
normal router exit, unused routes raise AllMockedAssertionError. If the test
body already raised another exception, assert_all_called is skipped so the
original error is preserved.
Pytest Marker Configuration
The pytest plugin accepts these marker keyword arguments:
assert_all_mockedassert_all_calledbase_url
Unknown marker keyword arguments raise pytest.UsageError so typos fail early.
Matching Order and Precedence
Routes are matched by the active router only. Nested routers use the innermost active router first; outer routers are restored when inner contexts exit.
Within one router:
- Exact
method + URLroutes are checked first. - If multiple exact routes share the same key, the most recently registered route wins.
- Non-exact routes, including regex/callable/pattern routes, are checked in reverse registration order. Exact-route precedence is higher than fallback route recency.
- If no route matches,
assert_all_mocked=TrueraisesNoMockAddress; withassert_all_mocked=False, the request uses the originalniqueststransport.
Diagnostics intentionally show request method/URL and route summaries, but avoid printing header values or body contents by default.
Side Effects
Use side_effect when a route needs custom logic. The callable receives the
niquests.models.PreparedRequest and must return a niquests.Response or raise
an exception.
import niquests
from niquests_mock import MockRouter, build_response
with MockRouter() as router:
def create_job(request):
return build_response(request, status_code=201, json={"id": 1})
router.post("https://api.example.test/jobs").mock(side_effect=create_job)
response = niquests.post("https://api.example.test/jobs", json={"name": "build"})
Exceptions are recorded on Call.exception before being re-raised.
with MockRouter() as router:
route = router.get("https://api.example.test/fails").mock(
side_effect=RuntimeError("boom"),
)
For async requests, side effects may be async def callables or return awaitables.
import niquests
from niquests_mock import MockRouter, build_response
async def test_async_side_effect():
async with MockRouter() as router:
async def get_status(request):
return build_response(request, status_code=200, json={"ok": True})
router.get("https://api.example.test/status").mock(side_effect=get_status)
response = await niquests.arequest("GET", "https://api.example.test/status")
assert response.json() == {"ok": True}
Async Usage
MockRouter supports async context-manager use and patches niquests async
send calls for the active context.
async def test_async_context_manager():
async with MockRouter(base_url="https://api.example.test") as router:
router.get("/health").respond(json={"ok": True})
response = await niquests.arequest("GET", "https://api.example.test/health")
assert response.json() == {"ok": True}
Concurrency Notes
The active router is stored in a Python ContextVar.
- Async tasks created inside an active
MockRoutercontext inherit that active router context and can use the same registered routes. - Nested routers are task-local: the innermost active router handles requests for the current context, then the previous router is restored when the inner context exits.
- New threads do not automatically inherit the active router context. If code under
test performs HTTP calls in another thread, create or start a
MockRouterin that thread, or explicitly propagate the Python context yourself. - The
niquestssend methods are patched process-wide while at least one router is active, but route selection still depends on the current context. A patched send call with no active router in its context falls back to the original transport.
Compatibility Notes vs RESPX
niquests-mock is RESPX-like for common pytest workflows, but it is not a full RESPX
clone. This package targets niquests, not httpx.
Supported workflows:
- pytest fixture style via
niquests_mock; respx_mockfixture alias for easier migration from RESPX-shaped tests;- decorator style via
@niquests_mock.mock; - context-manager style via
MockRouter; - named routes and
lookup(); - sync and async
niquestsrequests; - strict unmatched-request failures via
NoMockAddress; - route-level pass-through;
- exact URL matching and fallback pattern matching;
- method, URL, scheme, host, path, headers, query params, content, and JSON matchers;
- route call assertions and router
assert_all_called.
Unsupported RESPX behavior should be treated as out of contract unless it is documented in this README or covered by tests.
Current non-goals:
- full RESPX API parity;
httpxtransport mocking;- automatic propagation of active routers into newly created threads;
- advanced route indexing for very large fallback route sets;
- a plugin system for custom matcher classes;
- stable internals for
MockRouterpatch lifecycle or route storage; - supporting
niquestsversions below the package requirement inpyproject.toml.
niquests-mock patches niquests.sessions.Session.send and
niquests.async_session.AsyncSession.send. Compatibility depends on those methods
continuing to accept a prepared request and keyword arguments in the shape used by
current supported niquests versions. Regression tests cover sync and async delegation
to the original send methods for pass-through and unmatched requests.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file niquests_mock-0.4.0.tar.gz.
File metadata
- Download URL: niquests_mock-0.4.0.tar.gz
- Upload date:
- Size: 13.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5de1ad98d0c504e0f3dc70fcbf95fd0524f0001e6d48820d2205892cdf70a2e1
|
|
| MD5 |
b46ee79a84442dfa1dd292b83e147c08
|
|
| BLAKE2b-256 |
c2eef72d08790ce3c8b1a779fe780f0b1ad220304745a206f97c71e4296f324c
|
File details
Details for the file niquests_mock-0.4.0-py3-none-any.whl.
File metadata
- Download URL: niquests_mock-0.4.0-py3-none-any.whl
- Upload date:
- Size: 17.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.21 {"installer":{"name":"uv","version":"0.11.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c1c4902f4fc6975ecd6e2dfd5ec1575515bdb465c7e696b44e3d93fd1c67ae5d
|
|
| MD5 |
36d03db6539e2a4fef78e1729bbac4e3
|
|
| BLAKE2b-256 |
c99ea677253e9ea85a052d730c8752a176a585d6146c5902242220ba0c97b723
|