skip to content

In Django testing, what is the difference between the test Client and RequestFactory, and when would you reach for each?

level: juniorimportance: must knowfreq 62%

answer

  1. one drives the stack, one builds
  2. which layers run between request and view
  3. request.user is your job with one
  4. URL resolution and template capture

basics

~20 s

Django's test Client runs a request through URL routing, every middleware and template rendering, like an in-process browser. RequestFactory only builds a request object you pass to one view yourself, with no middleware, so you set request.user and session by hand.

solid answer

~40 s

`django.test.Client` (available as `self.client` on Django's test cases) takes a path, resolves it through the URLconf, runs the request through everything in `MIDDLEWARE`, calls the view and hands back a response enriched with `context`, `templates`, `wsgi_request` and, with `follow=True`, `redirect_chain`. It keeps cookies between calls, so a login sticks. `RequestFactory` shares the method API (`get()`, `post()` and friends) but only *builds* an `HttpRequest`: there is no URL resolution and no middleware, so `request.user`, `request.session` and message storage do not exist until you attach them, and you call the view yourself, for example `DashboardView.as_view()(request)`. I use the Client when I want to know that a URL behaves end to end inside Django, and RequestFactory when I want a fast, focused test of one view's logic with exactly controlled inputs.

code

python · 8 lines
python
from django.test import RequestFactory
from dashboard.views import DashboardView

request = RequestFactory().get("/dashboard/")
request.user = user          # AuthenticationMiddleware did not run
view = DashboardView()
view.setup(request)          # prepare a CBV without dispatching it
context = view.get_context_data()

go deeper

for a junior

Know that self.client runs the full request path and that RequestFactory just builds a request you pass to one view. Be able to show both in a few lines.

for a middle

Explain exactly which layers RequestFactory skips (middleware, URL resolution, cookies) and what you must attach by hand: request.user, session, message storage.

for a senior

Show judgement about coverage: protection enforced in middleware or urls.py decorators is invisible to RequestFactory tests, so security-relevant paths need Client tests.

for a principal

Frame the split as a team convention: Client tests for route contracts and permissions, RequestFactory tests for view internals, and a rule for which one a new view needs.

## Two tools in `django.test` Django ships two ways to put a fake HTTP request in front of your code without starting a server. Both live in `django.test`, both accept the same request-building arguments (`data`, `headers`, `query_params`, `secure`, `content_type` and extra WSGI keys), and both return control to your test immediately. The difference is **how much of Django runs** between the request and your view. ## What the test Client runs `django.test.Client` behaves like a scripted browser that lives inside the same Python process: 1. It builds a WSGI environ for the path you give it (the host defaults to `testserver`). 2. Its handler loads the project's `MIDDLEWARE` chain, exactly as the real WSGI handler does, and passes the request through it. 3. The URL resolver matches the path against `ROOT_URLCONF`, so a typo in `urls.py` or a missing route fails the test. 4. The view runs, templates render, and the response travels back out through the middleware. The response comes back decorated with test-only attributes: - **`status_code`**, **`content`** and **`json()`** for the payload. - **`context`** and **`templates`**, captured from the template-rendered signal (populated only for the `DjangoTemplates` backend). - **`wsgi_request`**, the request object the handler actually built, handy for inspecting `request.user` after middleware ran. - **`redirect_chain`**, when you pass `follow=True`. The Client also keeps a cookie jar, so `self.client.force_login(user)` followed by `self.client.get("/dashboard/")` behaves like a signed-in browser. By default it **does not enforce CSRF** (`enforce_csrf_checks=False`) and it **re-raises view exceptions** in your test (`raise_request_exception=True`). ## What RequestFactory gives you `RequestFactory` builds the same kind of request object and then stops. You receive a `WSGIRequest` and are responsible for everything the stack would normally have done: - **No middleware runs.** `SessionMiddleware`, `AuthenticationMiddleware` and `MessageMiddleware` never touch the request, so `request.session`, `request.user` and message storage are absent until you set them. - **No URL resolution.** The path you pass only populates `PATH_INFO`; URL keyword arguments must be passed to the view call yourself. - **No `follow`.** It never makes a second request, because it never makes a first one. - **You call the view.** A function view is called as `dashboard(request)`; a class-based view as `DashboardView.as_view()(request)`, or instantiated and prepared with `view.setup(request)` when you want to test one method such as `get_context_data()` in isolation. ## Side by side | Aspect | `Client` | `RequestFactory` | |---|---|---| | URL resolution | yes, through `ROOT_URLCONF` | no, you pick the view | | Middleware | full `MIDDLEWARE` chain | none | | `request.user` / session | set by middleware from cookies | you assign them | | Cookies between calls | kept | not applicable | | `follow=True` | supported | not accepted | | `response.context` / `templates` | captured | not captured | | Typical use | a URL's behaviour inside Django | one view's logic, fast and isolated | ## A worked example ```python from django.contrib.auth.models import AnonymousUser from django.test import RequestFactory, TestCase from django.urls import reverse from accounts.models import User from dashboard.views import DashboardView class DashboardTests(TestCase): @classmethod def setUpTestData(cls): cls.user = User.objects.create_user("ana", password="pw") def test_through_the_stack(self): self.client.force_login(self.user) response = self.client.get(reverse("dashboard")) self.assertEqual(response.status_code, 200) self.assertEqual(response.wsgi_request.user, self.user) def test_view_logic_only(self): request = RequestFactory().get("/dashboard/") request.user = self.user # no AuthenticationMiddleware here response = DashboardView.as_view()(request) self.assertEqual(response.status_code, 200) ``` ## Choosing between them - Reach for the **Client** when the question is "what does this URL do for this kind of user?": routing, authentication, redirects, CSRF behaviour you opt into, template choice and rendered output all participate. - Reach for **RequestFactory** when the question is "does this view (or this view method) compute the right thing for these inputs?", especially when setting up the whole stack would be slow or noisy, or when you want to test a mixin or `get_queryset()` directly. - Remember what RequestFactory cannot tell you: anything enforced by middleware or by decorators applied in `urls.py` is invisible to it. Most Django codebases lean on the Client for the bulk of view tests and add RequestFactory tests for tricky view internals. For async views the same split exists as `AsyncClient` and `AsyncRequestFactory`. ## Pitfalls that trip people up - **Forgetting URL kwargs.** `RequestFactory().get("/projects/7/")` does not give the view `pk=7`; call `ProjectView.as_view()(request, pk=7)`. - **Forgetting the session.** Views that read or write `request.session` fail on a factory request until you attach a session object yourself. - **Asserting on unrendered output.** A class-based view returns a `TemplateResponse`; its `content` is available only after `response.render()`, which the Client does for you and a direct call does not. - **Over-trusting a green factory test.** It proves the view's logic for the inputs you built, not that the URL, the middleware or the login requirement around it behave.

  • Why does a view that calls messages.success() fail under RequestFactory but not under the Client?
    `messages.success()` stores the message through storage that `MessageMiddleware` attaches to the request. RequestFactory runs no middleware, so the storage is missing and Django raises `MessageFailure`. Under the Client the middleware runs and the call works. With RequestFactory you either attach session and message storage yourself or test that path with the Client.
  • Can you use assertTemplateUsed on a response produced by calling a view with a RequestFactory request?
    Not with a response argument: the template names are recorded by the test Client from the template-rendered signal, and `assertTemplateUsed(response, name)` raises `ValueError` saying it is only usable on responses fetched with the Django test Client. You can instead use `assertTemplateUsed` as a context manager around the view call, or check `response.template_name` on a `TemplateResponse`.

The Client is a mystery shopper who walks in through the front door, past security and the greeter, to the counter; RequestFactory is a supplier who hands the clerk a sealed order directly at the counter, so nobody at the door ever checks it.

saying these in an interview costs you the question

  • Claiming RequestFactory runs middleware but skips the database
  • Believing RequestFactory requests resolve URLs and fill URL kwargs automatically
  • Thinking the test Client opens a network socket to a running server
  • Assuming request.user exists on a RequestFactory request by default
  • Saying the test Client enforces CSRF tokens by default