Async network code is easy to get subtly wrong; fast in-process tests catch it without real servers.
aiohttp ships its own testing tools in aiohttp.test_utils:
TestServer(app)starts yourweb.Applicationon a random local port inside the current event loop.TestClient(server)is aClientSessionbound to that server, so you callawait client.get("/path")with relative URLs.AioHTTPTestCaseintegrates them withunittest.IsolatedAsyncioTestCase.
With pytest, install pytest-aiohttp (which uses pytest-asyncio) and use the aiohttp_client fixture:
async def test_hello(aiohttp_client):
client = await aiohttp_client(create_app())
resp = await client.get("/")
assert resp.status == 200Fixtures also exist for raw servers (aiohttp_server, aiohttp_raw_server). Write an app factory function (create_app()) so each test gets a fresh app with its own state.
For testing client code, the same tools help: start a small fake web.Application with TestServer that mimics the external API (including errors and slow responses with asyncio.sleep), and point your client at server.make_url("/"). This tests real HTTP behaviour, including timeouts and headers. Libraries like aioresponses instead patch ClientSession to return canned responses without any server.
Remember that startup and cleanup signals run with TestServer, so app resources such as database pools are created and closed per test.
Example
import asyncio
from aiohttp import ClientSession, ClientTimeout, web
from aiohttp.test_utils import TestServer
async def get_price(session, base_url, sku): # client code under test
async with session.get(f"{base_url}/price/{sku}") as resp:
resp.raise_for_status()
return (await resp.json())["price"]
async def fake_price(request): # fake external API
if request.match_info["sku"] == "slow":
await asyncio.sleep(2)
return web.json_response({"price": 9.5})
async def test():
app = web.Application()
app.router.add_get("/price/{sku}", fake_price)
async with TestServer(app) as server:
base = str(server.make_url("")).rstrip("/")
async with ClientSession(timeout=ClientTimeout(total=0.5)) as s:
assert await get_price(s, base, "abc") == 9.5
try:
await get_price(s, base, "slow")
except TimeoutError:
print("timeout handled") # timeout handled
asyncio.run(test())Remember
- TestServer + TestClient run apps in-process; pytest-aiohttp provides aiohttp_client
- Use an app factory so each test gets fresh state
- Fake external APIs with a small TestServer app to test client code
Watch out for
- Sharing one module-level app with mutable state across tests.