In Django, when do you need AsyncClient or AsyncRequestFactory instead of Client and RequestFactory, and what changes in how you call them?
answer
- async def tests
- ASGIRequest instead of WSGIRequest
- await every request method
- headers without the HTTP_ prefix
basics
~20 sUse AsyncClient inside async def tests, or to exercise Django's ASGI request path; each request method must be awaited and the view gets an ASGIRequest. AsyncRequestFactory builds ASGIRequest objects with synchronous methods, for calling an async view directly.
solid answer
~40 sAn `async def` test method on a Django test case runs in its own event loop, and from there you use `self.async_client` (an `AsyncClient`): `response = await self.async_client.get("/dashboard/")`. It has the same methods as `Client`, drives the request through Django's asynchronous handler, and the view receives an `ASGIRequest`; sync views still work because the async path adapts them. Logging in is `await self.async_client.aforce_login(user)` (added in Django 5.0). Headers go in `headers={...}`, or as extra keyword arguments without the `HTTP_` prefix the sync client needs. `AsyncRequestFactory` is `RequestFactory` for ASGI: its methods stay synchronous and return an `ASGIRequest`, and you `await` the async view you call with it. As with `RequestFactory`, no middleware runs.
go deeper
Know that async def tests use self.async_client and that every request call on it must be awaited.
Explain ASGIRequest versus WSGIRequest, the a-prefixed login helpers, the header-name difference, and why AsyncRequestFactory methods stay synchronous.
Anticipate the failure modes: sync ORM calls in async tests, non-async-aware decorators, and suites that only ever test async views through the sync client.
Decide how far the suite follows an async adoption: which paths deserve ASGI-path tests and which can stay on the simpler synchronous tools.
## Two async twins Django's test tools come in synchronous and asynchronous pairs: | Sync | Async | Request type produced | |---|---|---| | `Client` (`self.client`) | `AsyncClient` (`self.async_client`) | `WSGIRequest` / `ASGIRequest` | | `RequestFactory` | `AsyncRequestFactory` | `WSGIRequest` / `ASGIRequest` | The async variants matter in two situations: when the **test itself is a coroutine** (`async def test_...`), and when you want to exercise the **ASGI request path** that production uses under an ASGI server. ## `AsyncClient` Django detects `async def` test methods on its test case classes and runs each in its own event loop. Inside one, the synchronous Client is the wrong tool because it would block that loop, so every Django test case also provides `self.async_client`: ```python from django.test import TestCase from django.urls import reverse from accounts.models import User class AsyncDashboardTests(TestCase): async def test_dashboard_for_signed_in_user(self): user = await User.objects.acreate(username="ana") await self.async_client.aforce_login(user) response = await self.async_client.get( reverse("dashboard"), headers={"accept": "text/html"} ) self.assertEqual(response.status_code, 200) ``` Key differences from `Client`: - **Every request method is a coroutine**, so `get()`, `post()` and the rest must be awaited. - **Session helpers have `a`-prefixed versions**: `alogin()`, `aforce_login()` and `alogout()`, plus `asession()`, all added in Django 5.0. - **The view receives an `ASGIRequest`**, not a `WSGIRequest`. Sync views still run, because Django's async handler adapts them, so you can test either kind of view. - **Headers:** the `headers` dictionary works on both clients. When passing headers as extra keyword arguments, `Client` needs WSGI names such as `HTTP_ACCEPT`, while `AsyncClient` takes them without the `HTTP_` prefix (`ACCEPT="application/json"`), and its extra constructor defaults go straight into the ASGI scope. - The same defaults as `Client`: CSRF checks off unless `enforce_csrf_checks=True`, exceptions re-raised unless `raise_request_exception=False`. ## `AsyncRequestFactory` `AsyncRequestFactory` is API-compatible with `RequestFactory` with one difference: it returns **`ASGIRequest`** instances with a correct ASGI scope. Its methods are **still synchronous** because building a request needs no I/O. You then await the async view yourself: ```python from django.test import AsyncRequestFactory from dashboard.views import dashboard_feed # async def view async def test_feed(self): request = AsyncRequestFactory().get("/dashboard/feed/") request.user = self.user # no middleware, as with RequestFactory response = await dashboard_feed(request) self.assertEqual(response.status_code, 200) ``` Everything true of `RequestFactory` still holds: **no middleware**, no URL resolution, and you attach `request.user` or a session yourself. ## Pitfalls in async tests 1. **Forgetting `await`.** `response = self.async_client.get(...)` gives a coroutine, not a response; the assertion then fails with an attribute error or, worse, a warning about a never-awaited coroutine. 2. **Calling the sync ORM from the coroutine.** Querying with sync methods inside an `async def` test raises `SynchronousOnlyOperation`; use the async QuerySet methods (`acreate()`, `aget()`, `acount()`) or wrap sync code with `sync_to_async`. 3. **Decorators that are not async-aware.** Django's docs warn that third-party test decorators on an `async def` test may wrap the wrong thing; the workaround is to apply them to a sync method that wraps the coroutine with `async_to_sync`. 4. **Assuming `AsyncClient` means an ASGI server.** It calls Django's async handler in-process; no server or socket is involved. ## When the sync tools are still fine An async *view* does not force an async *test*. `self.client.get()` from an ordinary `def` test can call an async view; Django bridges it. Choose `AsyncClient` when the test body needs to `await` things, or when you want the request to take the ASGI path end to end. ## Quick reference - `self.async_client` is created for every Django test case alongside `self.client`; a custom class can be plugged in through `async_client_class`. - Constructor: `AsyncClient(enforce_csrf_checks=False, raise_request_exception=True, *, headers=None, query_params=None, **defaults)`. - `query_params=` (Django 5.1+) works on both clients and both factories for adding a query string to any method, including `post()`. - Responses expose the same test attributes as the sync client (`status_code`, `json()`, `context`, `templates`), with `asgi_request` in place of `wsgi_request`. In an interview, the distinguishing points are short: await every request, use the `a`-prefixed login helpers, expect an `ASGIRequest`, and remember that `AsyncRequestFactory` builds requests synchronously and runs no middleware, exactly like its synchronous sibling.
- Does testing an async view require an async def test with AsyncClient?No. The synchronous `Client` can request a URL served by an async view from an ordinary `def` test, and Django adapts between the two. You need `AsyncClient` when the test body itself is a coroutine, for example because it awaits async ORM calls, or when you want the request built as an `ASGIRequest` through the async handler.
saying these in an interview costs you the question
- Believing AsyncRequestFactory methods must be awaited
- Thinking AsyncClient starts an ASGI server and makes network calls
- Assuming AsyncClient can only call async views
- Passing HTTP_ACCEPT as an extra keyword to AsyncClient as with Client
- Calling self.async_client.force_login() and awaiting the result