Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
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. This makes tests fast, deterministic, and independent of network conditions.
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 — use a local server with
httpxoraiohttptest utilities
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
@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"},
)
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
@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
@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
@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
@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
@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"
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
-
For a deeper guide, see Stub External HTTP Services with WireMock.
-
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 complex logic that can’t be expressed with static responses -
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. - Not matching the exact URL:
responsesmatches URLs exactly by default. Usematch_querystring=Trueor regex URLs for flexibility. - 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 verify your code’s logic, not that the real API works. Use contract tests for that.
FAQ
How do I match URLs with regex?
import re
responses.add(
method=responses.GET,
url=re.compile(r"https://api\.example\.com/users/\d+"),
json={"id": 1},
)
How do I simulate a connection error?
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:
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. Use unittest.mock.patch when you want to mock at the function or method level. responses is more realistic because it tests the actual HTTP call path.
How do I mock streaming responses?
import responses
@responses.activate
def test_streaming():
def stream_callback(request):
body = iter([b"chunk1\n", b"chunk2\n", b"chunk3\n"])
return (200, {"Content-Type": "text/plain"}, body)
responses.add_callback(
responses.GET,
"https://api.example.com/stream",
callback=stream_callback,
)
Can I mock responses conditionally based on request body?
Yes. Use add_callback to inspect the request and return different responses:
@responses.activate
def test_conditional():
def callback(request):
if b"premium" in request.body:
return (200, {}, json.dumps({"plan": "premium"}))
return (200, {}, json.dumps({"plan": "free"}))
responses.add_callback(responses.POST, "https://api.example.com/subscribe", callback=callback) 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.
RecipeSetup Test Fixtures
How to manage test fixtures with factory patterns, setup/teardown hooks, and deterministic data for reliable unit and integration tests across Python, JavaScript, and Java.
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 Test Coverage with pytest-cov
How to measure and enforce Python test coverage thresholds with pytest-cov, including branch coverage, HTML reports, exclusions, and 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.