# getsentry/responses: mocking the Python requests library in tests

> responses registers fake HTTP responses for the requests library, so unit tests stop touching the network. It is small, it is maintained by Sentry, and it has one failure mode worth knowing before you adopt it.

**getsentry/responses** — A utility for mocking out the Python Requests library.

- Repository: https://github.com/getsentry/responses
- Stars: 4,343 · Forks: 385
- Language: Python
- License: Apache-2.0
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/getsentry-responses

## What getsentry/responses replaces in a test suite

Any Python test that calls requests.get against a real host carries three costs: it needs a network, it needs the remote service to be up, and its result changes when the remote data changes. responses removes all three by intercepting requests inside a decorated test and answering from responses you registered yourself. The README describes it plainly as "a utility library for mocking out the requests Python library."

The audience is narrow and specific. You are writing unit tests for code that calls requests, and you want those tests to run offline and return fixed data. If your code talks to an internal service over a client library that does not use requests underneath, responses has nothing to intercept. The same applies to async HTTP clients. The README covers requests and nothing else, and setup.py lists requests>=2.30.0,<3.0 as an install requirement.

## How activation and URL matching actually work

The core mechanism is a decorator, responses.activate, wrapped around a test function. Inside that function, requests is patched so that outgoing calls are matched against a registry you filled with responses.add or one of the method shortcuts. A registered response carries a method, a URL, a status, and a body or JSON payload. When a call matches, requests returns the registered object instead of opening a socket.

The matching rule is the part people trip over. If no registered response matches the URL, responses raises ConnectionError rather than letting the request through. The README shows exactly this: a test that calls requests.get on an unregistered URL is expected to raise ConnectionError from requests.exceptions. That default is a design choice with teeth. It means a typo in a URL inside your own code turns into a test failure instead of a silent live call, which is usually what you want, but it also means any request your code makes that you forgot to register will fail loudly. You can supply your own match callbacks as a tuple to control matching on request attributes beyond the URL.

Registration has two shapes. You can construct a responses.Response with method and url and pass the object to responses.add, or pass the arguments directly. Shortcuts such as responses.get, responses.post and responses.patch prefill the method. There is also a context manager form, responses.RequestsMock(), for tests where a decorator does not fit. The README notes that outside the context manager block, requests goes back to hitting the remote server.

## Installing responses and writing a first test

Installation is a single pip command. The README requires Python 3.8 or newer and requests 2.30.0 or newer, and setup.py records the same floor.

```bash
pip install responses
```

A first test registers one GET response and asserts on the parsed JSON. The decorator is what activates the patching; without it, responses.add has no effect on the live requests module.

```python
import responses
import requests

@responses.activate
def test_simple():
    responses.add(
        responses.GET,
        "http://twitter.com/api/1/foobar",
        json={"error": "not found"},
        status=404,
    )
    resp = requests.get("http://twitter.com/api/1/foobar")
    assert resp.json() == {"error": "not found"}
    assert resp.status_code == 404
```

Run it with pytest and the test passes without any network traffic. If you change the URL in the request to one you did not register, the test raises ConnectionError instead, which is the behaviour the README documents. For tests that need to check a request body or headers, the match argument takes a tuple of callbacks, and the deprecated match_querystring flag should be replaced with responses.matchers.query_param_matcher or responses.matchers.query_string_matcher.

## Where responses stops being the right tool

The largest limitation is scope. responses patches requests. Code that uses httpx, aiohttp, or urllib3 directly is not intercepted, and the README does not claim otherwise. If your project has migrated part of its HTTP layer away from requests, you will end up with two mocking strategies in one test suite.

The second limitation is the strict default. Unmatched URLs raise ConnectionError, which is documented but easy to forget when you add a new endpoint to production code and forget the corresponding registration. The failure is loud, which is better than a silent live call, but it is a failure you will meet often in a fast-moving codebase.

Third, the deprecation table in the README is substantial. match_querystring, the stream argument on Response, and the module-level names responses.assert_all_requests_are_fired, responses.passthru_prefixes and responses.target have all been deprecated and moved. Code written against older versions of responses will still run but will carry migration debt. The README gives a migration path for each entry, so the work is mechanical rather than risky.

## How responses differs from VCR.py and pytest-httpserver

The closest alternative is VCR.py, which records real HTTP interactions to a cassette file and replays them. The difference in approach matters. responses makes you declare each response by hand in the test, so the fixture is explicit and reviewable, and a change to the expected payload shows up as a diff in the test file. VCR.py captures whatever the server returned at record time, which is faster to set up but means the fixture can drift from the contract without anyone noticing until the cassette is re-recorded.

pytest-httpserver takes a third route: it starts a real local HTTP server and points your code at it. That exercises the socket layer and works with any client, not just requests, but it requires your code to accept a configurable base URL. responses works with hardcoded URLs because it intercepts at the requests layer. If your code builds URLs from a settings object, pytest-httpserver is viable; if it calls absolute URLs directly, responses fits without refactoring. The repository's own test dependencies include pytest-httpserver, which suggests the maintainers use both.

## Maintenance, versioning and licence

The repository is not archived, and the last push was on 2026-09-22. Recent releases are 0.26.1 on 2026-05-21, 0.26.2 on 2026-07-03, and 0.26.3 on 2026-08-26. The version numbering stays in the 0.x range, which means no compatibility promise across minor versions; the deprecation table is the practical substitute, listing when each piece of functionality was deprecated and what to use instead.

Upgrade cost is low for a library of this size. The install requirements are requests, urllib3 and pyyaml, and the package ships a py.typed marker, so type checkers pick up the annotations. Python 3.8 is the floor, which is worth checking against your own support matrix.

The licence is Apache-2.0, declared in both setup.py and the LICENSE file at the repository root. Apache-2.0 permits commercial use and modification and includes an explicit patent grant. It also requires that you preserve copyright and licence notices in redistributed copies. That is a summary of the licence text, not legal advice; read LICENSE and your own counsel's guidance if you are redistributing the library.

## Conclusion

Adopt responses if your test suite makes HTTP calls through requests and you want those calls answered deterministically without a network. Do not adopt it if your code uses httpx, aiohttp or urllib3 directly, because the README describes mocking only the requests library. Before you commit, check that your requests version is at least 2.30.0, since setup.py pins requests>=2.30.0,<3.0, and confirm that an unmatched URL raising ConnectionError is the behaviour your tests expect.

## FAQ

### How do I install getsentry/responses?

The README gives a single command: pip install responses. It requires Python 3.8 or newer and requests 2.30.0 or newer.

### What happens in getsentry/responses if a request URL is not registered?

The README states that responses raises a ConnectionError when a fetched URL does not hit a match, and shows a test that expects requests.exceptions.ConnectionError.

### Which HTTP libraries does getsentry/responses mock?

It mocks the requests library only. The README describes it as a utility library for mocking out the requests Python library, and setup.py lists requests>=2.30.0,<3.0 as an install requirement.

### What is the licence for getsentry/responses?

The licence is Apache-2.0, declared in setup.py and present as a LICENSE file at the repository root. Apache-2.0 permits commercial use and modification and requires preserving copyright and licence notices on redistribution.

### Can I use getsentry/responses without the decorator?

Yes. The README documents a context manager form, responses.RequestsMock(), and notes that outside the context manager block requests will hit the remote server again.

## Sources

- [getsentry/responses on GitHub](https://github.com/getsentry/responses)
- [Issues](https://github.com/getsentry/responses/issues)
- [License: Apache-2.0](https://github.com/getsentry/responses/blob/master/LICENSE)
- [README](https://github.com/getsentry/responses/blob/master/README.md)
- [Releases](https://github.com/getsentry/responses/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/getsentry-responses
