mrkeyoor.com_
Sun 20 Sept 17:51 UTC
PyPITestingupdated 20 Sept 2026

responses review

responses 0.26.2 replaces outbound calls made through Python requests with registered in-memory results. A test activates the patch, associates a method and URL with JSON, bytes, an exception, or a callback, and can inspect every matched call afterward. Built-in matchers cover query parameters, JSON, forms, headers, multipart uploads, URL fragments, and selected request arguments. Unknown requests raise requests.exceptions.ConnectionError unless passthrough is enabled. Version 0.26.2 fixes numeric query matching that changed the caller's dictionary and removes recorded default headers regardless of their letter case, including lowercase HTTP/2-style names.

Verdict

responses 0.26.2 installed in 0.3 seconds, used 6 MB across 7 packages, and imported in 0.59 seconds with 0 audit findings in our sandbox. Use it for precise requests unit tests; use a real test server when the network stack itself is part of the claim.

We installed it

Lab card: what happened when we installed responsesScreenshot of responses documentation
Install✓ · 0.3s7 packages on disk · 6 MB
Importimport responses in 0.59s · pure Python · py.typed · requires Python >=3.8
Known vulns0(pip-audit)

Answers from our run

Does responses install cleanly?

Yes. In a fresh container with an empty cache, pip install responses finished in 0.3s, leaving 7 packages and 6 MB on disk. pip-audit reported no known vulnerabilities.

What does responses need to run?

Python >=3.8, and nothing compiled: it is pure Python. In our run import responses succeeded in 0.59s, and the package ships py.typed for type checkers.

responses or requests-mock: which should you use?

requests-mock: Choose it for adapter mounting, a pytest fixture, or mocking one requests Session rather than process-wide activation. responses 0.26.2 installed in 0.3 seconds, used 6 MB across 7 packages, and imported in 0.59 seconds with 0 audit findings in our sandbox.

When should you not use responses?

The client is HTTPX or aiohttp. responses patches requests.adapters.HTTPAdapter.send and will not intercept those transports.

API stability4/5Registration through add or method helpers, activation, RequestsMock, matchers, callbacks, and call history remains familiar across the 0.x line. The README also lists deprecated names and their replacements. The package is still pre-1.0, and the recorder uses underscored beta entry points, so ordinary stubs have a stronger compatibility record than stored-cassette automation.
Docs4/5The README documents simple stubs, all built-in matchers, exceptions, callbacks, response sequences, redirects, retries, passthrough, call inspection, registries, and recording with executable examples. Troubleshooting is more scattered: request mismatch details arrive inside ConnectionError text, and decorator versus context defaults require careful reading instead of one concise behavior table.
Maintenance4/5Release 0.26.2 shipped on July 3, 2026, and the repository was pushed on July 24, 2026. GitHub reports 42 open issues and pull requests, 4,345 stars, and an unarchived repository under Sentry. The current patch fixes both recorded-header normalization and caller-data mutation, concrete correctness bugs rather than cosmetic release churn.
Ecosystem5/5The package records 15,517,173 weekly downloads and follows the requests API vocabulary used across many Python SDKs. It works with ordinary Session-based clients and fits pytest or unittest. Its boundary is also clear: HTTPX, aiohttp, and socket-level testing each need different tools, so applications using several HTTP clients must maintain more than one mocking approach.

Use it if

  • Application or SDK code sends HTTP through requests or a normal requests.Session adapter.
  • Unit tests must assert the exact query, JSON, form, header, multipart, or request-option contract.
  • A retry path needs a deterministic series of failures and successes with no remote server.
  • Tests need decorator, context-manager, fixture, callback, and call-history options around one patched transport.
Skip it if

Setup reality

Our responses 0.26.2 install completed in 0.3 seconds in a fresh Python 3.12 Bookworm container. It left 7 packages using 6 MB, and import responses took 0.59 seconds. pip-audit found 0 known vulnerabilities. The pure-Python package declares 14 direct dependencies, requires Python >=3.8, includes py.typed, and uses Apache-2.0.

Activation replaces requests.adapters.HTTPAdapter.send. A custom adapter or a call through urllib3, HTTPX, or aiohttp can still reach the network. Keep an independent network-deny rule in CI if accidental traffic is unacceptable. Passthrough prefixes deliberately restore real I/O, so restrict them to a precise host or route rather than https://.

Use a decorator, RequestsMock context, or yielding fixture and make the unused-registration policy explicit with assert_all_requests_are_fired. Manual start() requires unconditional stop() and reset() in teardown. Otherwise the next test can inherit both the patched adapter and old registrations, producing order-dependent failures.

Matchers compare the request that requests actually prepares. Strict headers include defaults inserted by the client; query expectations should live in either the URL or a matcher, not both. A mismatch surfaces as ConnectionError, which retry code may catch and repeat. Check call history and registered routes before blaming networking. Recorder YAML can contain authorization headers, tokens, URLs, and personal data, so scrub it before committing.

Patterns

Return JSON from one GET stub-json-get

import requests
import responses

@responses.activate
def test_user():
    responses.get('https://api.example.com/users/7', json={'id': 7}, status=200)
    reply = requests.get('https://api.example.com/users/7')
    assert reply.json() == {'id': 7}

The json argument also sets the response content type. An unregistered URL raises ConnectionError.

Require an exact JSON request match-json-body

from responses import matchers

responses.post(
    'https://api.example.com/orders',
    json={'id': 'ord_1'},
    match=[matchers.json_params_matcher({'sku': 'abc', 'qty': 2})],
)

The matcher compares parsed JSON structure. A body mismatch appears as an unmatched request rather than a custom assertion.

Match query parameters match-query

responses.get(
    'https://api.example.com/search',
    json={'hits': []},
    match=[matchers.query_param_matcher({'q': 'ada', 'page': '2'})],
)
requests.get('https://api.example.com/search', params={'q': 'ada', 'page': 2})

Keep the query out of the registered URL when using this matcher. Version 0.26.2 fixes mutation of numeric input values.

Check selected request headers match-headers

responses.get(
    'https://api.example.com/data',
    json={},
    match=[matchers.header_matcher({'Accept': 'application/json'})],
)

Non-strict matching allows default headers that requests adds. Strict mode requires accounting for the fully prepared request.

Return a failure before success sequence-replies

responses.get('https://api.example.com/job', status=503)
responses.get('https://api.example.com/job', json={'ready': True}, status=200)

assert requests.get('https://api.example.com/job').status_code == 503
assert requests.get('https://api.example.com/job').json()['ready'] is True

Multiple matching registrations are consumed in order. Disable retry sleep when testing production retry loops.

Simulate a requests timeout raise-timeout

responses.get(
    'https://api.example.com/slow',
    body=requests.exceptions.ConnectTimeout('timed out'),
)
with pytest.raises(requests.exceptions.ConnectTimeout):
    requests.get('https://api.example.com/slow', timeout=1)

Raise the same requests exception class that production code handles; a generic Exception exercises a different branch.

Calculate a response from the request dynamic-callback

def callback(request):
    payload = json.loads(request.body)
    return 200, {'Content-Type': 'application/json'}, json.dumps({'sum': sum(payload['values'])})

responses.add_callback(responses.POST, 'https://calc.example.com/sum', callback=callback)

Callbacks return status, headers, and body. Decode request.body according to its real type and content type.

Assert the matched request history inspect-history

route = responses.patch('https://api.example.com/users/7', status=204)
requests.patch('https://api.example.com/users/7', json={'active': False})

assert route.call_count == 1
assert json.loads(route.calls[0].request.body) == {'active': False}

Per-route history stays readable when several unrelated calls occur in the same activated test.

Clean up through a pytest fixture scope-fixture

@pytest.fixture
def api_mock():
    with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
        rsps.get('https://api.example.com/health', json={'ok': True})
        yield rsps

The context unpatches requests even when the test raises. Manual start() needs stop() and reset() in teardown.

Permit one real host allow-passthrough

responses.add_passthru('https://storage.example.com/public/')
responses.get('https://api.example.com/data', json={})

Passthrough makes real network calls. A broad prefix such as `https://` removes the protection against accidental traffic.

Change a registered result mid-test replace-response

responses.get('https://api.example.com/config', json={'flag': False})
responses.replace(responses.GET, 'https://api.example.com/config', json={'flag': True})
assert requests.get('https://api.example.com/config').json()['flag'] is True

replace requires an existing route. Use upsert when the route may not have been registered yet.

Alternatives

PackageRegistryPick it when
requests-mockPyPIChoose it for adapter mounting, a pytest fixture, or mocking one requests Session rather than process-wide activation.
pytest-httpserverPyPIChoose it when a real local server should exercise the client's socket and HTTP behavior.
httpxPyPIChoose HTTPX's MockTransport when production code already uses HTTPX and should keep one client-specific test stack.

More testing guides

pytest · chai · vitest · jsdom · playwright · coverage · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.