RESPX Documentation

repository·master·Indexed 21 days ago

https://github.com/lundberg/respx

A utility for mocking HTTPX and HTTP Core requests using request patterns and response side effects. It provides a @respx.mock decorator, a pytest fixture (respx_mock), and a @pytest.mark.respx marker for configuration. Features include a Router for managing mocks, support for complex URL matching via the M() function and bitwise operators, and utilities for mocking request bodies, headers, and cookies.

Tokens
17K
Snippets
53
Records
58
Agent score
73%

What's inside RESPX

  1. Mock HTTPX requests with RESPX

    master

    RESPX acts as a mock router for httpx. It captures outgoing httpx requests and allows you to define mocked responses based on specific request patterns.

    Routing is achieved by matching requests against Route objects. A Route is composed of request patterns (such as host, method, path, etc.) and lookups. When a captured request matches a Route, RESPX can:

    1. Resolve to a mocked httpx.Response.
    2. Trigger a specific side effect.
    3. Be marked to pass through, allowing the original request to proceed without mocking.

    Routing logic is inspired by the Django ORM query API, using bitwise operators to combine patterns.

  2. Use MockTransports for targeted mocking (Version 0.14.0)

    master

    Instead of patching the global HTTPX transport, you can use specific transport classes. This is useful when you want to pass a specific transport to an httpx.Client or httpx.AsyncClient instance directly.

    • MockTransport: The base class for all mocking. respx.mock is an instance of this.
    • SyncMockTransport: Use this for synchronous httpx.Client instances.
    • AsyncMockTransport: Use this for asynchronous httpx.AsyncClient instances.

    Mock transports accept the same configuration arguments as the respx.mock decorator/context manager.

    import httpx
    import respx
    
    # Using SyncMockTransport with a Client
    mock_transport = respx.SyncMockTransport()
    request = mock_transport.get("https://foo.bar/", content="foobar")
    
    with httpx.Client(transport=mock_transport) as client:
        response = client.get("https://foo.bar/")
        assert request.called
        assert response.status_code == 200
        assert response.text == "foobar"
    
    # Using AsyncMockTransport with an AsyncClient
    async def test_async():
        mock_transport = respx.AsyncMockTransport()
        request = mock_transport.get("https://foo.bar/", content="foobar")
    
        async with httpx.AsyncClient(transport=mock_transport) as client:
            response = await client.get("https://foo.bar/")
            assert request.called
            assert response.status_code == 200
            assert response.text == "foobar"
  3. Mock with side effects (functions, exceptions, or iterables)

    master

    RESPX supports side_effect mocking, which behaves similarly to Python's built-in Mock object. side_effect takes precedence over return_value.

    Function side effects

    Functions are called with the captured request argument. They can:

    • Return an httpx.Response.
    • Raise an Exception.
    • Return None to treat the route as a non-match (continuing to the next route).
    • Return the input Request to pass the request through.
    • Advanced: If the route uses regex with named groups, those groups are passed as kwargs to the function. You can also include a route argument to access call stats or modify the route.

    Exception side effects

    Pass an httpx.HTTPError subclass or any Exception to simulate request errors.

    Iterable side effects

    Pass an iterable (like a list) to return/raise different responses for repeated requests in order. Once the iterable is exhausted, a StopIteration is raised. You can use itertools to create infinite sequences.

    import httpx
    import respx
    
    # 1. Function side effect with request and route
    def my_side_effect(request, route):
        return httpx.Response(201, json={"id": route.call_count + 1})
    
    @respx.mock
    def test_side_effect():
        respx.post("https://example.org/").mock(side_effect=my_side_effect)
        
        response = httpx.post("https://example.org/")
        assert response.json() == {"id": 1}
    
    # 2. Regex named groups passed as kwargs
    def my_side_effect_kwargs(request, slug):
        return httpx.Response(200, json={"slug": slug})
    
    @respx.mock
    def test_regex_kwargs():
        route = respx.route(url__regex=r"https://example.org/(?P<slug>\w+)/")
        route.side_effect = my_side_effect_kwargs
        
        response = httpx.get("https://example.org/foobar/")
        assert response.json() == {"slug": "foobar"}
    
    # 3. Exception side effect
    @respx.mock
    def test_connection_error():
        respx.get("https://example.org/").mock(side_effect=httpx.ConnectError)
        # ... triggers error
    
    # 4. Iterable side effect
    @respx.mock
    def test_stacked_responses():
        route = respx.get("https://example.org/")
        route.side_effect = [httpx.Response(404), httpx.Response(200)]
        # First call returns 404, second returns 200
  4. Combine patterns using bitwise operators

    master

    You can combine multiple Pattern objects using bitwise operators to create complex matching logic.

    • AND (&): Combines two patterns; both must match.
    • OR (|): Combines two patterns; either must match.
    • INVERT (~): Inverts a pattern; matches if the pattern does not match.
    # AND
    M(scheme="http") & M(host="example.org")
    
    # OR
    M(method="PUT") | M(method="PATCH")
    
    # INVERT
    ~M(params={"foo": "bar"})
    M(scheme="http") & M(host="example.org")
    M(method="PUT") | M(method="PATCH")
    ~M(params={"foo": "bar"})
  5. Combine patterns using the M() object

    master

    Use the M() object and bitwise operators (& for AND, | for OR, ~ for NOT) to create complex, reusable request patterns.

    Note: M(url="//example.org/foobar/") is equivalent to M(host="example.org") & M(path="/foobar/").

    import httpx
    import respx
    from respx import M
    
    hosts_pattern = M(host="example.org") | M(host="example.com")
    my_route = respx.route(hosts_pattern, method="GET", path="/foo/")
    
    response = httpx.get("http://example.org/foo/")
    assert my_route.called
  6. Mock multiple sequential responses with the same pattern

    master

    If you define multiple mocks for the same request pattern, RESPX will match them in the order they were defined. Each match 'pops' the pattern until the last one is reached.

    import httpx
    import respx
    
    @respx.mock
    def test_something():
        respx.get("https://foo.bar/baz/123/", status_code=404)
        respx.get("https://foo.bar/baz/123/", content={"id": 123})
    
        response = httpx.get("https://foo.bar/baz/123/")
        assert response.status_code == 404  # First match
    
        response = httpx.get("https://foo.bar/baz/123/")
        assert response.json() == {"id": 123}  # Second match
  7. QuickStart: Mock HTTPX requests with respx.mock

    master

    To mock HTTPX requests, use the @respx.mock decorator on your test function. Inside the decorated function, define routes using methods like respx.get() or respx.post() and call .mock(return_value=...) to specify the response.

    import httpx
    import respx
    
    from httpx import Response
    
    
    @respx.mock
    def test_example():
        my_route = respx.get("https://example.org/").mock(return_value=Response(204))
        response = httpx.get("https://example.org/")
        assert my_route.called
        assert response.status_code == 204
  8. Simulate request errors (e.g. Connection Timeouts)

    master

    To test how your application handles network failures, pass an exception instance (like those from httpcore) to the content parameter of a mock.

    import httpx
    import httpcore
    import respx
    
    @respx.mock
    def test_something():
        respx.get("https://foo.bar/", content=httpcore.ConnectTimeout())
        # httpx.get("https://foo.bar/") will now raise the exception
  9. Migrate from requests-mock to respx

    master

    If you are migrating from requests-mock, follow these patterns:

    Patching the Client

    • Decorator: Use @respx.mock() and accept respx_mock: respx.Router as an argument.
    • Context Manager: Use with respx.mock() as respx_mock:.

    Mocking and Assertions

    • Mocking: Use respx_mock.get().respond(json={}) instead of m.get(..., json={}).
    • Exceptions: Set side_effect on the route (e.g., respx.post().side_effect = Exception()) instead of using the exc parameter.
    • List of Responses: Pass a list to side_effect instead of passing a list to the mock method.
    • Assertions:
      • Use respx.calls.assert_called_once() instead of m.called_once.
      • Access the last request via respx.calls.last.request and check properties like .url or .content (use json.loads(request.content) for JSON bodies).
    # Example: requests-mock to respx migration
    @respx.mock()
    def test_some_call(self, respx_mock: respx.Router):
        respx_mock.get().respond(json={})
    
    # Example: Assertions
    respx.calls.assert_called_once()
    assert str(respx.calls.last.request.url) == "https://api.io/example/endpoint"
  10. Configure RESPX with pytest fixtures (Version 0.14.0)

    master

    To use RESPX in pytest, create a fixture that uses respx.mock as a context manager. This allows you to define endpoints and aliases that can be accessed in your test functions.

    Tip: Use a session scoped fixture (@pytest.fixture(scope="session")) if your fixture contains multiple endpoints that are not all called by a single test case, or if you want to disable the built-in assert_all_called check.

    # conftest.py
    import pytest
    import respx
    
    @pytest.fixture
    def mocked_api():
        with respx.mock(base_url="https://foo.bar") as respx_mock:
            respx_mock.get("/users/", content=[], alias="list_users")
            yield respx_mock
    
    # test_api.py
    import httpx
    
    def test_list_users(mocked_api):
        response = httpx.get("https://foo.bar/users/")
        request = mocked_api["list_users"]
        assert request.called
        assert response.json() == []
  11. Migrate response mocking to the new API

    master

    Starting from RESPX version 0.15.0, response details are no longer passed as arguments directly to the request matching methods (like .post(), .get(), etc.). Instead, you must use .mock() or .respond() to define the response, or use the % operator for dictionary-based responses. This change separates request pattern matching from response configuration.

    Migration Summary:

    • Use .mock(return_value=Response(...)) for full control.
    • Use .respond(...) for quick response definitions.
    • Use the % operator for shorthand dictionary responses.
    • Replace .add() with .route().
    # Previously
    respx.post("https://some.url/", status_code=200, content={"x": 1})
    
    # Now (Option 1: Using .mock)
    respx.post("https://some.url/").mock(return_value=Response(200, json={"x": 1}))
    
    # Now (Option 2: Using .respond)
    respx.post("https://some.url/").respond(200, json={"x": 1})
    
    # Now (Option 3: Using % operator)
    respx.post("https://some.url/") % dict(json={"x": 1})
    
    # Migrating .add() to .route()
    respx.route(method="POST", url="https://some.url/").respond(content="foobar")
  12. Use Reusable Routers

    master

    You can create isolated router instances by calling respx.mock(...). These are useful for grouping routes per API and mocking them individually or stacking them.

    Named routes in a reusable router can be accessed directly via my_mock_router[<route_name>].

    import httpx
    import respx
    
    # Create an isolated router
    api_mock = respx.mock(base_url="https://api.foo.bar/", assert_all_called=False)
    api_mock.get("/baz/", name="baz").mock(
        return_value=httpx.Response(200, json={"name": "baz"}),
    )
    
    @api_mock
    def test_decorator():
        response = httpx.get("https://api.foo.bar/baz/")
        assert response.status_code == 200
        assert response.json() == {"name": "baz"}
        assert api_mock["baz"].called
    
    def test_ctx_manager():
        with api_mock:
            ...