aiohttp routes tested offline with the pytest-aiohttp test client: working versions (tested 2026-10-15)
An aiohttp JSON app exercised through the pytest-aiohttp in-process test client, plus a conftest that brings loopback up so the client runs inside a network-isolated sandbox.
passed5 passed · 0 failed2026-10-15 21:56 UTC- Status
- passed
- Last passed
- 2026-10-15 21:56 UTC
- Tests
- 5 / 5
- Language
- Python
- Runtime
- Python 3.13.16
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt .venv/bin/python -m pytest -q
Python 3.13.16 linux 6.17.0-1022-azure x64 runner: github-actions 38813530570
The code
Exactly the files that passed. Your agent can pull them with get_recipe("aiohttp-pytest-plugin-client") (install), or see them on GitHub.
aiohttp==3.14.5 pytest-aiohttp==1.1.1 pytest==9.1.1 pytest-asyncio==1.4.0
from aiohttp import web
def make_app():
app = web.Application()
app["users"] = {1: {"id": 1, "name": "Ada"}, 2: {"id": 2, "name": "Linus"}}
app.router.add_get("/health", health)
app.router.add_get("/users/{id}", get_user)
app.router.add_post("/users", create_user)
return app
async def health(request):
return web.json_response({"status": "ok"})
async def get_user(request):
user = request.app["users"].get(int(request.match_info["id"]))
if user is None:
raise web.HTTPNotFound(text="user not found")
return web.json_response(user)
async def create_user(request):
payload = await request.json()
name = payload.get("name")
if not name:
raise web.HTTPBadRequest(text="name is required")
users = request.app["users"]
user = {"id": max(users) + 1, "name": name}
users[user["id"]] = user
return web.json_response(user, status=201)
import fcntl
import socket
import struct
import pytest
_IFF_UP = 0x1
_SIOCGIFFLAGS = 0x8913
_SIOCSIFFLAGS = 0x8914
def _enable_loopback():
try:
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
ifreq = struct.pack("16sH", b"lo", 0)
flags = struct.unpack("16sH", fcntl.ioctl(sock, _SIOCGIFFLAGS, ifreq))[1]
if not flags & _IFF_UP:
fcntl.ioctl(sock, _SIOCSIFFLAGS, struct.pack("16sH", b"lo", flags | _IFF_UP))
except OSError:
pass
@pytest.fixture(scope="session", autouse=True)
def loopback():
_enable_loopback()
import aiohttp
import pytest
from app import make_app
async def test_health(aiohttp_client):
client = await aiohttp_client(make_app())
resp = await client.get("/health")
assert resp.status == 200
assert resp.content_type == "application/json"
assert await resp.json() == {"status": "ok"}
async def test_missing_user_raises(aiohttp_client):
client = await aiohttp_client(make_app())
resp = await client.get("/users/99")
assert resp.status == 404
with pytest.raises(aiohttp.ClientResponseError) as exc:
resp.raise_for_status()
assert exc.value.status == 404
async def test_create_then_fetch(aiohttp_client):
client = await aiohttp_client(make_app())
created = await client.post("/users", json={"name": "Grace"})
assert created.status == 201
body = await created.json()
assert body["name"] == "Grace"
fetched = await client.get(f"/users/{body['id']}")
assert await fetched.json() == body
async def test_invalid_payload_is_400(aiohttp_client):
client = await aiohttp_client(make_app())
resp = await client.post("/users", json={})
assert resp.status == 400
assert await resp.text() == "name is required"
async def test_fresh_app_isolates_state(aiohttp_client):
first = await aiohttp_client(make_app())
second = await aiohttp_client(make_app())
await first.post("/users", json={"name": "Grace"})
resp = await second.get("/users/3")
assert resp.status == 404
[pytest] pythonpath = . asyncio_mode = auto
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
aiofiles async file IO coordinated by anyio under pytest-asyncio
Write and read files with aiofiles, fan out concurrent reads with an anyio task group, serialize appends with anyio.Lock, and test it all offline with pytest-asyncio.
anyio to_thread and from_thread bridging sync work under pytest-asyncio
Run blocking code in worker threads with anyio.to_thread, call back into async code from those threads with anyio.from_thread, and test the whole bridge offline with pytest-asyncio.
Async repository pattern with SQLAlchemy 2 AsyncSession on aiosqlite
A typed async repository over SQLAlchemy 2 AsyncSession with eager loading, foreign keys enforced on SQLite, and offline pytest-asyncio tests against an in-memory database.
asyncio TaskGroup, timeouts and semaphores tested with pytest-asyncio
Bounded concurrent fetches with asyncio.Semaphore, per-call asyncio.timeout and TaskGroup error handling, tested offline with pytest-asyncio loop scope config and async fixtures.
All aiohttp recipesAll pytest-aiohttp recipesAll pytest-asyncio recipes