Async ASGI TestClient
Async ASGI TestClient is a framework-agnostic library for testing web applications that implement the ASGI specification (versions 2 and 3). It allows direct interaction with an ASGI application within the same asyncio loop as the tests, eliminating the need for a separate HTTP server. Inspired by Quart's testing module, it supports features like cookies, multipart/form-data, redirects, and streaming for both requests and responses, as well as websocket testing. The current version is 1.4.11.
Common errors
-
RuntimeError: Task <Task ...> got Future <Future ...> attached to a different loop
cause Attempting to use asynchronous resources (like database connections or other async clients) within an `async def` test function that itself uses a testing client (like `Starlette.TestClient` or `FastAPI.TestClient`) that creates its own internal event loop or sync-to-async bridge. This is not specific to `async-asgi-testclient` but a common pattern problem.fixWhen writing `async def` tests, ensure all async operations are part of the same event loop. `async-asgi-testclient` itself runs within the test's event loop, so this error is less likely with this specific library unless other async components introduce a separate loop. If using other `TestClient` implementations, switch to an `AsyncClient` from `httpx` with `ASGITransport` if you need to perform multiple async operations within the same test function. -
TestClient calls wrong method when fastapi.APIRouter().add_api_router() is used to setup the router
cause Specific routing configurations within FastAPI using `APIRouter().add_api_router()` may not be correctly interpreted by `async-asgi-testclient` in some scenarios.fixThis is an active issue. Possible workarounds might involve refactoring routing to be more direct, or ensuring the ASGI application passed to `TestClient` is the final, fully configured app. Check GitHub issues for `async-asgi-testclient` for status or community-provided solutions. -
Streaming not working with newer versions of starlette
cause Compatibility issues between `async-asgi-testclient`'s streaming handling and updates in newer Starlette versions.fixThis is an open issue. Ensure you are on a version of `async-asgi-testclient` that explicitly supports your Starlette version for streaming. If the problem persists, check the GitHub repository for updates or specific version recommendations. -
Client unable to handle websocket connection rejections
cause The `TestClient` may not correctly process or expose reasons for rejected WebSocket connections from the ASGI application.fixThis is an open issue. When testing WebSocket rejections, examine the ASGI application's logic for rejecting connections and the `TestClient`'s response. You might need to inspect lower-level attributes of the response object or application logs for more details.
Warnings
- gotcha Mixing synchronous `TestClient` instances (from frameworks like Starlette/FastAPI's built-in `TestClient`) with asynchronous test functions that access other async resources (e.g., database connections) can lead to event loop errors due to context mixing. `async-asgi-testclient` is designed to be fully asynchronous.
- gotcha When testing WebSocket connections, ensure proper closure. The `TestClient`'s `ws_session` method handles closing via an `async with` context manager, but if using `ws_connect` directly, you must call `websocket.close()` manually.
- gotcha Some users have reported issues with `TestClient` when a FastAPI application uses `APIRouter().add_api_router()` for routing, leading to incorrect method calls.
Install
-
pip install async-asgi-testclient
Imports
- TestClient
from async_asgi_testclient import TestClient
Quickstart
import pytest
from quart import Quart, jsonify
from async_asgi_testclient import TestClient
# A dummy ASGI app for testing
app = Quart(__name__)
@app.route('/')
async def root():
return 'plain response'
@app.route('/json')
async def json_endpoint():
return jsonify({"hello": "world"})
@pytest.mark.asyncio
async def test_quart_app():
async with TestClient(app) as client:
# Test GET request to root
resp = await client.get("/")
assert resp.status_code == 200
assert resp.text == "plain response"
# Test GET request to JSON endpoint
resp = await client.get("/json")
assert resp.status_code == 200
assert resp.json() == {"hello": "world"}