Usage ===== Interception modes ------------------ ``mock_external_urls=False`` (default) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The server starts on localhost but DNS is **not** patched. Point your client directly at ``m.server_url``. This is the safest mode — no global state is modified. .. code-block:: python async with aiointercept() as m: m.get(f"{m.server_url}/api/users", payload=[{"id": 1}]) async with aiohttp.ClientSession() as session: resp = await session.get(f"{m.server_url}/api/users") assert resp.status == 200 Use ``m.server_url`` as a ``base_url`` to keep your code clean: .. code-block:: python async with aiointercept() as m: m.get(f"{m.server_url}/api/users", payload=[{"id": 1}]) async with aiohttp.ClientSession(base_url=m.server_url) as session: resp = await session.get("/api/users") ``mock_external_urls=True`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Patches the DNS resolver at the **process level** so every aiohttp request is redirected to the mock server — even those made by third-party libraries you cannot modify. .. code-block:: python async with aiointercept(mock_external_urls=True) as m: m.get("https://api.stripe.com/v1/charges", payload={"data": []}) # Code under test calls the real Stripe URL internally result = await billing_service.list_charges() assert result == [] .. warning:: DNS patching affects the whole process for the duration of the block. It does not intercept requests to bare IP addresses. Registering mock responses -------------------------- ``add(url, method, ...)`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python m.add( url, # str | yarl.URL | re.Pattern method="GET", # HTTP method (case-insensitive) status=200, body=b"", # raw response body (str is UTF-8 encoded) json=None, # serialized to JSON, overrides body payload=None, # alias for json headers=None, # extra response headers content_type=None, # overrides Content-Type repeat=False, # True = infinite; int N = exactly N times callback=None, # callable or coroutine -> CallbackResult reason=None, # HTTP reason phrase exception=None, # truthy -> close connection (ClientConnectionError) ) HTTP method shortcuts ~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python m.get(url, **kwargs) m.post(url, **kwargs) m.put(url, **kwargs) m.patch(url, **kwargs) m.delete(url, **kwargs) m.head(url, **kwargs) m.options(url, **kwargs) All shortcuts forward their keyword arguments to :meth:`~aiointercept.aiointercept.add`. Regex patterns ~~~~~~~~~~~~~~~ Pass a compiled :class:`re.Pattern` to match a family of URLs: .. code-block:: python import re pattern = re.compile(r"^https://api\.example\.com/users/\d+$") m.get(pattern, payload={"id": 1, "name": "Alice"}) # Matches https://api.example.com/users/1, /users/42, etc. Repeat and response queuing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python # Respond to every request (indefinite): m.get(url, repeat=True, payload={"ok": True}) # Respond exactly 3 times, then raise ClientConnectionError: m.get(url, repeat=3, status=200) # Queue different responses by calling add() multiple times: m.post(url, status=201, payload={"created": True}) m.post(url, status=409, payload={"error": "conflict"}) # First POST -> 201, second POST -> 409, third POST -> ClientConnectionError Callbacks --------- Use a callback when the response depends on the request: .. code-block:: python from aiointercept import aiointercept, CallbackResult def echo_callback(url, *, headers, query, json, **kwargs): return CallbackResult(status=200, payload={"echo": json}) async def test_echo(): async with aiointercept() as m: m.post(f"{m.server_url}/echo", callback=echo_callback) ... Async callbacks are also supported: .. code-block:: python async def async_callback(url, **kwargs): await asyncio.sleep(10) return CallbackResult(body=b"async response") async def test_slow(): async with aiointercept() as m: m.get(f"{m.server_url}/slow", callback=async_callback) ... A callback returns a :class:`~aiointercept.CallbackResult`: .. list-table:: :header-rows: 1 :widths: 18 22 14 46 * - Field - Type - Default - Description * - ``status`` - ``int`` - ``200`` - Response status code * - ``body`` - ``str | bytes`` - ``""`` - Raw response body * - ``payload`` - ``Any`` - ``None`` - Response body serialized to JSON (overrides ``body``) * - ``headers`` - ``dict[str, str] | None`` - ``None`` - Extra response headers * - ``content_type`` - ``str`` - ``"application/json"`` - ``Content-Type`` header * - ``reason`` - ``str | None`` - ``None`` - HTTP reason phrase Passthrough ----------- Let specific hosts or all unmatched requests reach the real network. Only available with ``mock_external_urls=True``. .. code-block:: python # Specific hosts bypass the mock: async with aiointercept( mock_external_urls=True, passthrough=["https://real-api.example.com"], ) as m: m.get("https://mocked.example.com/data", payload={"mocked": True}) # Requests to real-api.example.com go to the real server. # All unmatched requests go to the real server: async with aiointercept( mock_external_urls=True, passthrough_unmatched=True, ) as m: m.get("https://mocked.example.com/data", payload={"mocked": True}) # Any other URL is proxied to the real network. Inspecting requests ------------------- All intercepted requests are stored in ``m.requests``, keyed by ``(METHOD, normalized_url)``: .. code-block:: python from yarl import URL key = ("POST", URL("https://api.example.com/orders")) req = m.requests[key][-1] # most recent request req.captured_body # raw bytes body req.kwargs["json"] # parsed JSON body (or None) req.kwargs["query"] # dict[str, list[str]] - preserves duplicate keys req.kwargs["headers"] # raw request headers (multidict) URLs are normalized: fragments are stripped and query parameters are sorted. Constructor parameters ---------------------- .. list-table:: :header-rows: 1 :widths: 22 18 12 48 * - Parameter - Type - Default - Description * - ``mock_external_urls`` - ``bool`` - ``False`` - When ``True``, patches the DNS resolver so external URLs are intercepted. When ``False``, only requests to ``m.server_url`` are intercepted. * - ``passthrough`` - ``list[str] | None`` - ``None`` - Hosts whose requests bypass the mock and reach the real network. Requires ``mock_external_urls=True``. * - ``passthrough_unmatched`` - ``bool`` - ``False`` - Proxy all unmatched requests to the real network. Requires ``mock_external_urls=True``. * - ``param`` - ``str | None`` - ``None`` - Kwarg name under which the mock is injected when used as a decorator.