Overview
The responses library intercepts HTTP requests made by the requests library in Python tests. Instead of hitting real external services, responses returns predefined mock responses — your suite stops being flaky because every call gets the same canned answer, in milliseconds, with no network involved.
When to Use
- Testing code that calls third-party REST APIs (payment gateways, email services, SMS providers)
- Simulating API error responses (500s, timeouts, rate limits) without a real service
- Verifying that your code sends the correct request body, headers, and query parameters
- Running tests in CI without network access or API keys
When NOT to Use
- Testing your own API endpoints — use a test client (e.g., FastAPI
TestClient, DjangoTestCase) - Integration tests that need a real database or message queue — use Testcontainers instead
- Load testing — mocks don’t reflect real-world latency or throughput
- Testing webhook receivers or flows that need a standalone stub server — use WireMock, MSW, or API mocking tools in general
Solution
Setup
pip install responses pytest
Basic mock response
import responses
import pytest
import requests
@responses.activate
def test_get_user():
responses.add(
method=responses.GET,
url="https://api.example.com/users/1",
json={"id": 1, "name": "Alice", "email": "alice@example.com"},
status=200,
)
resp = requests.get("https://api.example.com/users/1")
assert resp.status_code == 200
assert resp.json()["name"] == "Alice"
Mock with headers and query params
import responses
import requests
from responses import matchers
@responses.activate
def test_search_with_params():
responses.add(
method=responses.GET,
url="https://api.example.com/search",
json={"results": [{"title": "Python Guide"}]},
status=200,
headers={"X-RateLimit-Remaining": "99"},
match=[matchers.query_param_matcher({"q": "python"})],
)
resp = requests.get(
"https://api.example.com/search",
params={"q": "python", "page": 1},
headers={"Authorization": "Bearer test-token"},
)
assert resp.headers["X-RateLimit-Remaining"] == "99"
assert resp.json()["results"][0]["title"] == "Python Guide"
# Verify the request was made correctly
assert len(responses.calls) == 1
assert "q=python" in responses.calls[0].request.url
Mock error responses
import responses
import pytest
import requests
@responses.activate
def test_handle_server_error():
responses.add(
method=responses.GET,
url="https://api.example.com/users/1",
status=500,
json={"error": "Internal Server Error"},
)
with pytest.raises(requests.HTTPError):
resp = requests.get("https://api.example.com/users/1")
resp.raise_for_status()
@responses.activate
def test_handle_timeout():
responses.add(
method=responses.GET,
url="https://api.example.com/slow-endpoint",
body=requests.exceptions.Timeout("Connection timed out"),
)
with pytest.raises(requests.exceptions.Timeout):
requests.get("https://api.example.com/slow-endpoint")
Mock multiple responses for the same URL
import responses
import requests
@responses.activate
def test_retry_logic():
responses.add(
method=responses.GET,
url="https://api.example.com/data",
status=503,
)
responses.add(
method=responses.GET,
url="https://api.example.com/data",
status=200,
json={"data": "success"},
)
# First call fails
resp1 = requests.get("https://api.example.com/data")
assert resp1.status_code == 503
# Second call succeeds
resp2 = requests.get("https://api.example.com/data")
assert resp2.status_code == 200
assert resp2.json()["data"] == "success"
Using a callback for dynamic responses
import json
import responses
import requests
@responses.activate
def test_callback_response():
def request_callback(request):
payload = request.body
if b"premium" in payload:
return (200, {}, json.dumps({"tier": "premium", "quota": 10000}))
return (200, {}, json.dumps({"tier": "free", "quota": 100}))
responses.add_callback(
method=responses.POST,
url="https://api.example.com/subscribe",
callback=request_callback,
content_type="application/json",
)
resp = requests.post(
"https://api.example.com/subscribe",
json={"plan": "premium"},
)
assert resp.json()["quota"] == 10000
Asserting on request body
import json
import responses
import requests
@responses.activate
def test_post_request_body():
responses.add(
method=responses.POST,
url="https://api.example.com/orders",
status=201,
json={"id": 42, "status": "created"},
)
requests.post(
"https://api.example.com/orders",
json={"product_id": 10, "quantity": 3},
)
sent_body = json.loads(responses.calls[0].request.body)
assert sent_body["product_id"] == 10
assert sent_body["quantity"] == 3
Using responses as a pytest fixture
import responses
import pytest
import requests
@pytest.fixture
def mock_api():
with responses.RequestsMock() as rsps:
yield rsps
def test_with_fixture(mock_api):
mock_api.add(
method=responses.GET,
url="https://api.example.com/health",
status=200,
json={"status": "healthy"},
)
resp = requests.get("https://api.example.com/health")
assert resp.json()["status"] == "healthy"
Runnable suite: every test on this page ships in the python-mock-external-apis-responses companion repo, including the conftest.py fixture.
Explanation
responses works by monkey-patching requests’ transport adapter. When a test decorated with @responses.activate (or running inside responses.RequestsMock()) calls requests.get, the call never reaches the socket — responses looks up the registered mocks and returns the first one that matches.
Matching happens in two stages: the URL (exact string or re.compile pattern) and the HTTP method, then any optional match list of matchers. A registered response that matches on URL but fails a matcher is skipped, not failed — responses keeps searching the registry, and if nothing matches at all the request raises ConnectionError, which is how forgotten mocks surface.
Two operational details matter in practice. First, registered responses for the same URL are consumed in order — that’s what powers the retry-sequence example above. Second, the decorator resets the registry on every test; if you manage RequestsMock yourself through a fixture, you control when mocks are added and when assert_all_requests_are_fired applies.
Variants
Using httpx with respx
If you use httpx instead of requests, use the respx library:
import respx
import httpx
@respx.mock
def test_httpx_mock():
respx.get("https://api.example.com/users/1").respond(
200, json={"id": 1, "name": "Alice"}
)
resp = httpx.get("https://api.example.com/users/1")
assert resp.json()["name"] == "Alice"
Using aioresponses for aiohttp
from aioresponses import aioresponses
import aiohttp
import pytest
@pytest.mark.asyncio
async def test_async_mock():
with aioresponses() as m:
m.get("https://api.example.com/data", payload={"key": "value"})
async with aiohttp.ClientSession() as session:
async with session.get("https://api.example.com/data") as resp:
data = await resp.json()
assert data["key"] == "value"
Best Practices
- Always use
@responses.activateor the context manager — without it, real HTTP calls go through - Assert on
responses.callsto verify your code sent the correct request - Use
add_callbackfor logic a static response can’t express, like branching on the request body - Reset
responses.callsbetween tests if you’re checking call counts - Mock at the HTTP layer, not at the function layer — this tests the real integration path
Common Mistakes
- Forgetting
@responses.activate: without it,responses.addraises an error or real requests go through. - Relying on exact URL matching:
responsesmatches URLs exactly by default, so a different query string silently misses your mock. Usematchers.query_param_matcher(orquery_string_matcher) in thematchlist —match_querystringwas deprecated in responses 0.17.0 — or a regex URL when the path varies. - Mocking too many endpoints: if every test mocks 10 endpoints, the test setup becomes the test. Extract shared mocks into fixtures.
- Not testing error paths: mock 500s, timeouts, and rate limits — these are the paths that fail in production.
- Using mocks for integration tests: mocks prove your parsing and error handling — they say nothing about whether the real API still works. For that, you need contract tests against the real service.
Frequently Asked Questions
How do I match URLs with regex?
import re
import responses
responses.add(
method=responses.GET,
url=re.compile(r"https://api\.example\.com/users/\d+"),
json={"id": 1},
)
How do I match on query parameters or headers?
Use the match list with helpers from responses.matchers:
from responses import matchers
responses.add(
method=responses.GET,
url="https://api.example.com/search",
json={"results": []},
match=[
matchers.query_param_matcher({"q": "python"}),
matchers.header_matcher({"Authorization": "Bearer test-token"}),
],
)
Avoid match_querystring=True — it was deprecated in responses 0.17.0.
How do I simulate a connection error?
import responses
import requests
responses.add(
method=responses.GET,
url="https://api.example.com/down",
body=requests.exceptions.ConnectionError("Connection refused"),
)
Can I mock streaming responses?
Yes, pass a generator to body:
import responses
def stream_generator():
yield b"chunk1\n"
yield b"chunk2\n"
responses.add(
method=responses.GET,
url="https://api.example.com/stream",
body=stream_generator(),
content_type="text/event-stream",
)
How do I verify how many times an endpoint was called?
assert len(responses.calls) == 3
# Or filter by URL
api_calls = [c for c in responses.calls if "api.example.com" in c.request.url]
assert len(api_calls) == 3
Should I use responses or unittest.mock.patch?
Use responses when your code uses the requests library. Reach for unittest.mock.patch when you need to fake a function or method rather than an HTTP call. responses exercises the real HTTP call path — headers, retries, raise_for_status — so it's the more realistic of the two.
Related Resources
Pytest Fixtures and Parametrize
How to use pytest fixtures and @pytest.mark.parametrize to write data-driven tests with reusable setup logic across Python projects.
RecipeWrite Unit Tests with Mocks and Stubs
How to isolate code under test using mock objects, stubs, and spies to replace external dependencies like databases, APIs, and file systems.
RecipeMeasure and Enforce Python Test Coverage with pytest-cov
Measure and enforce coverage with pytest-cov: branch coverage, HTML reports, exclusions, CI integration.
RecipeProperty-Based Testing with Hypothesis
How to use Hypothesis for property-based testing in Python, generating hundreds of test cases automatically from strategies instead of writing them by hand.
RecipeMock Network Requests with MSW
How to use Mock Service Worker (MSW) to intercept network requests in JavaScript tests and development, including REST and GraphQL mocking.
RecipeAPI Mocking for Testing
Build reliable tests by mocking external APIs with WireMock, MockServer, and MSW to eliminate flakiness and test edge cases.