aioresponses gives aiohttp tests a mocking decorator that speaks async
Aioresponses is a helper for mock/fake web requests in python aiohttp package.
At a glance
- What is it?
- aioresponses is a small helper for mocking HTTP requests made through aiohttp, usable as a decorator or a context manager, covering the six common methods, JSON payloads, headers, regex URLs, redirects and repeated responses for retry tests. It is MIT licensed, it needs Python 3.10 and aiohttp 3.8 or later, and its last tagged release is from 2022 while the branch was pushed in 2026.
- Who is it for?
- Adopt aioresponses if you test asyncio code that calls HTTP APIs through aiohttp and want to assert on status, body and headers without a network or a running server. Do not adopt it if you use httpx or requests rather than aiohttp, since it hooks into aiohttp specifically, and expect the project to be in maintenance mode rather than actively developed.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 122 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The gap it fills was in the async world
aioresponses is a helper to mock or fake web requests in the Python aiohttp package.
The reason it exists is stated plainly in the documentation. For the requests module there are a lot of packages that help with testing, naming httpretty, responses and requests-mock. When it comes to testing asynchronous HTTP requests it is a bit harder, at least at the beginning. The purpose of this package is to provide an easy way to test asynchronous HTTP requests.
So the design brief is narrow on purpose. It is not a general HTTP mocking framework with adapters for every client. It mocks aiohttp, and it does that in a way that fits how aiohttp code is written, which is asynchronously and usually inside a coroutine.
What it can mock is a status code, a body, a payload for a JSON response and headers, and the supported HTTP methods are GET, POST, PUT, PATCH, DELETE and OPTIONS.
The supported versions are Python 3.10 or newer and aiohttp 3.8 up to but not including 4.0, which is worth noting because the aiohttp 4 line is exactly where a mock built on aiohttp internals would be most likely to break.
A decorator or a context manager, and the same assertions either way
To mock out an HTTP request you use aioresponses as a method decorator or as a context manager, and the two forms support the same assertions.
The decorator form takes the mock as an argument to the test function:
import aiohttp
import asyncio
from aioresponses import aioresponses
@aioresponses()
def test_request(mocked):
loop = asyncio.get_event_loop()
mocked.get('http://example.com', status=200, body='test')
session = aiohttp.ClientSession()
resp = loop.run_until_complete(session.get('http://example.com'))
assert resp.status == 200
mocked.assert_called_once_with('http://example.com')The context manager form is the same idea with a with block, and the documentation recommends the payload argument for a JSON response:
def test_ctx():
loop = asyncio.get_event_loop()
session = aiohttp.ClientSession()
with aioresponses() as m:
m.get('http://test.example.com', payload=dict(foo='bar'))
resp = loop.run_until_complete(session.get('http://test.example.com'))
data = loop.run_until_complete(resp.json())
assert dict(foo='bar') == data
m.assert_called_once_with('http://test.example.com')The assert_called_once_with check at the end is the part that makes these worth writing, because it verifies the URL that was actually requested, not just that a response came back.
Headers are mocked too, with a case detail that will bite you
Any HTTP headers can be mocked, and the example includes a note that saves an afternoon of confusion. You pass connection as the header name, but you get Connection back capitalised, because underneath multidict is used to work with HTTP headers.
import asyncio
import aiohttp
from aioresponses import aioresponses
@aioresponses()
def test_http_headers(m):
loop = asyncio.get_event_loop()
session = aiohttp.ClientSession()
m.post(
'http://example.com',
payload=dict(),
headers=dict(connection='keep-alive'),
)
resp = loop.run_until_complete(session.post('http://example.com'))
# note that we pass 'connection' but get 'Connection' (capitalized)
# under the neath `multidict` is used to work with HTTP headers
assert resp.headers['Connection'] == 'keep-alive'
m.assert_called_once_with('http://example.com', method='POST')The capitalisation behaviour comes from the header container aiohttp itself uses, so it is not an arbitrary choice by the mocking library. It matches what a real response would do, which is the right behaviour, but it means a test asserting on a lowercase header name will fail against a mocked response and pass against a real one.
The method is also part of the assertion, so a test can verify that a POST went out as a POST rather than relying on the URL to identify the call.
Different responses for the same URL, and repeats for retry logic
Registering different responses for the same URL is supported, and the response that is used is the first one that has not been used yet. Two registrations for the same URL with status 500 and then status 200 mean the first request gets the error and the second gets the success.
On top of that there is the repeat argument, which exists for testing retrying mechanisms. By default repeat=False means the response is not repeated, and repeat=1 does the same. repeat=n repeats a response n times, and repeat=True repeats it indefinitely.
import asyncio
import aiohttp
from aioresponses import aioresponses
@aioresponses()
def test_multiple_responses(m):
loop = asyncio.get_event_loop()
session = aiohttp.ClientSession()
m.get('http://example.com', status=500, repeat=2)
m.get('http://example.com', status=200) # will take effect after two preceding calls
resp1 = loop.run_until_complete(session.get('http://example.com'))
resp2 = loop.run_until_complete(session.get('http://example.com'))
resp3 = loop.run_until_complete(session.get('http://example.com'))
assert resp1.status == 500
assert resp2.status == 500
assert resp3.status == 200The comment in that example is the rule: the second registration takes effect after the two preceding calls have consumed the repeated response. So the semantics are a queue of registrations, and repeat controls how many times each entry is served before the next one starts.
This is the feature that makes it possible to test a client with a backoff or retry loop, since a single static mock cannot express two failures followed by a success.
Regex URLs, redirects, and letting real requests through
URLs can be matched with regular expressions rather than exact strings, which matters when the query string varies. The example compiles a pattern and registers it in place of a literal URL:
def test_regexp_example(m):
loop = asyncio.get_event_loop()
session = aiohttp.ClientSession()
pattern = re.compile(r'^http://example\.com/api\?foo=.*$')
m.get(pattern, status=200)
resp = loop.run_until_complete(session.get('http://example.com/api?foo=bar'))
assert resp.status == 200Redirects are mocked too, and both absolute and relative Location headers are supported, with the response URL asserted after following them:
@aioresponses()
def test_redirect_example(m):
loop = asyncio.get_event_loop()
session = aiohttp.ClientSession()
# absolute urls are supported
m.get(
'http://example.com/',
headers={'Location': 'http://another.com/'},
status=307
)
resp = loop.run_until_complete(
session.get('http://example.com/', allow_redirects=True)
)
assert resp.url == 'http://another.com/'
# and also relative
m.get(
'http://example.com/',
headers={'Location': '/test'},
status=307
)
resp = loop.run_until_complete(
session.get('http://example.com/', allow_redirects=True)
)
assert resp.url == 'http://example.com/test'Then there is the passthrough, which lets you mock some hosts and hit the real ones. Passing a list of servers to passthrough makes requests to those servers actually happen:
import asyncio
import aiohttp
from aioresponses import aioresponses
@aioresponses(passthrough=['http://backend'])
def test_passthrough(m, test_client):
session = aiohttp.ClientSession()
# this will actually perform a request
resp = loop.run_until_complete(session.get('http://backend/api'))And passthrough_unmatched passes through every request that has not been explicitly mocked, which is the setting for a test that mocks one endpoint and lets the rest of the client behave normally.
A small package, and a release history that has stopped moving
The repository is small and well organised, and it shows the shape of a project that is small because its scope is small.
There is an aioresponses package directory, a tests directory, a setup.py that uses pbr for metadata with package_data pointing at a py.typed file, which is what gives the package inline type hints for consumers. There is a tox.ini for running the test suite across Python versions, a unittest.cfg, a .coveragerc, requirements.txt and requirements-dev.txt, a .landscape.yml configuration for a code quality service, a .pyup.yml for dependency updates, an AUTHORS.rst, a HISTORY.rst for the changelog and a CONTRIBUTING.rst.
The runtime requirements are two lines, packaging 22.0 or newer and aiohttp 3.8 up to but not including 4.0. The Makefile offers the usual set of targets plus lint with flake8, coverage with the coverage tool, and a release target that builds and uploads both an sdist and a wheel.
The release history is the part to weigh. The tagged versions are 0.7.4 from 2022-12-14, 0.7.3 from the same day, and 0.7.2 from 2021-04-13. The last push was on 2026-06-23. So the branch has been touched recently while the published releases have not moved since the end of 2022, and that combination usually means maintenance without new features. The project is MIT licensed and it is not archived, and the topics are aiohttp, asyncio, mocking and test.
For a test helper, that is a reasonable position to be in, since the aiohttp 3.x API it targets is stable, but it should be a decision rather than a surprise.
Editorial conclusion
Adopt aioresponses if you test asyncio code that calls HTTP APIs through aiohttp and want to assert on status, body and headers without a network or a running server. Do not adopt it if you use httpx or requests rather than aiohttp, since it hooks into aiohttp specifically, and expect the project to be in maintenance mode rather than actively developed. Before you pin it, check three things: the aiohttp version you run, since the supported range is 3.8 up to but not including 4.0, whether your team needs the passthrough options, and whether the last tagged release is recent enough for your policy, because the newest published version is 0.7.4 from 2022-12-14.
Frequently asked questions
How do I install aioresponses?
Run pip install aioresponses. The supported versions are Python 3.10 or newer and aiohttp 3.8 up to but not including 4.0, and the runtime requirements also include packaging 22.0 or newer.
How do I mock an aiohttp request in aioresponses?
Use aioresponses as a method decorator or as a context manager, register the response with m.get, m.post and so on for the URL, and then assert with m.assert_called_once_with. The status code, body, payload for JSON and headers can all be mocked, for the GET, POST, PUT, PATCH, DELETE and OPTIONS methods.
How do I test retry logic with aioresponses?
Use the repeat argument on a registration. repeat=False or repeat=1 serves the response once, repeat=n serves it n times and repeat=True serves it indefinitely. Register a second response for the same URL to take effect once the preceding calls have been consumed.
Can aioresponses match URLs with a regular expression?
Yes. Compile a pattern and register it in place of a URL string, for example pattern = re.compile(r'^http://example\.com/api\?foo=.*$') followed by m.get(pattern, status=200). That is useful when the query string varies between calls.
Is aioresponses still maintained?
The last push was on 2026-06-23, but the newest published release is 0.7.4 from 2022-12-14, with 0.7.3 from the same day and 0.7.2 from 2021-04-13. The project is MIT licensed and is not archived, so it looks like maintenance rather than active feature work.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/pnuckowski-aioresponses)Was this useful?