In Django REST Framework, when do you test a view with APIRequestFactory instead of APIClient, and what must you do by hand?
answer
- builds a request, routes nothing
- you call the view yourself
- viewset actions need a mapping
- unrendered response, no middleware
basics
~20 sAPIRequestFactory only builds an HttpRequest; you call the view directly, so there is no URL routing or middleware. Use it to unit-test a view class; you must map viewset actions, pass URL kwargs, authenticate with force_authenticate(request, ...) and render the response yourself.
solid answer
~40 s`APIClient` sends a request through URL resolution, middleware, the view and rendering; `APIRequestFactory` only builds a Django `HttpRequest` and leaves the rest to me. I use the factory when the view class itself is the unit — a base view, heavily branching logic, a view not routed yet. Then I do the router's and middleware's work by hand: `ClaimViewSet.as_view({'post': 'create'})` to bind actions, `view(request, pk=7)` for URL kwargs, the module-level `force_authenticate(request, user=user)` because there is no session, and `response.render()` before reading `response.content`. CSRF is off for factory requests unless I pass `enforce_csrf_checks=True`. For endpoint behaviour I default to `APIClient`, since routing and settings bugs hide from the factory.
code
python · 23 linesfrom django.contrib.auth import get_user_model
from django.test import TestCase
from rest_framework.test import APIRequestFactory, force_authenticate
from claims.views import ClaimViewSet
class ClaimViewSetUnitTests(TestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.user = get_user_model().objects.create_user(username="dana")
def test_create_sets_owner(self):
view = ClaimViewSet.as_view({"post": "create"})
request = self.factory.post("/unused/", {"amount": "9.99"}, format="json")
force_authenticate(request, user=self.user)
response = view(request)
self.assertEqual(response.status_code, 201, response.data)
self.assertEqual(response.data["owner"], self.user.pk)
response.render() # required before reading response.content
self.assertIn(b"9.99", response.content)go deeper
Know that APIRequestFactory makes a request object and you call the view yourself, while APIClient goes through URLs like a real client.
List what the factory skips and how you replace each part: action mapping, URL kwargs, force_authenticate(request, ...), response.render(), the CSRF flag.
Argue for APIClient as the default and the factory only for view-class units, since routing, middleware and settings bugs never show up in factory tests.
Set the suite's split between view-level and endpoint-level tests so fast isolated tests do not replace at least one routed test per endpoint.
## Two ways to drive a DRF view from a test `rest_framework.test` offers two tools that look similar and sit at different depths: - **`APIClient`** sends a request through Django's whole handler: **URL resolution**, the project's **middleware**, the view, rendering. You address the endpoint by URL, typically `reverse("claim-list")`. - **`APIRequestFactory`** only **builds a request object** — Django's plain `HttpRequest`, with the body encoded the same way `APIClient` would encode it. Nothing routes it. You call the view yourself, which makes it a unit test of the view rather than of the endpoint. `APIClient` actually subclasses `APIRequestFactory`, so the `format=` and encoding rules are identical; the difference is everything that happens after the request is built. ## What the factory leaves to you 1. **Get a callable view.** For an `APIView`, `ClaimDetail.as_view()`. For a viewset, `as_view()` needs the method-to-action mapping the router would normally supply: `ClaimViewSet.as_view({"post": "create"})`. Calling it without actions raises a `TypeError`. 2. **Pass URL keyword arguments by hand.** The path string in `factory.get("/api/claims/7/")` is never resolved, so the view gets `pk` only if you call `view(request, pk=7)`. 3. **Authenticate explicitly.** Middleware does not run, so there is no session user. Use the module-level `force_authenticate(request, user=user)` from `rest_framework.test` (the function, not the client method). Setting `request.user` yourself only works with `SessionAuthentication`, and setting attributes such as `request.token` does nothing, because the factory returns an `HttpRequest`, not DRF's `Request` — that wrapper is only built when the view runs. 4. **Render before reading bytes.** The view returns an unrendered DRF `Response`. `response.data` is usable at once; `response.content` raises until you call `response.render()`, because rendering normally happens in Django's request cycle. 5. **Opt in to CSRF if you want it.** Requests from the factory carry a flag that disables CSRF checks; `APIRequestFactory(enforce_csrf_checks=True)` removes it. DRF needs this flag because its CSRF check runs inside `SessionAuthentication`, in the view, not in middleware. ## When each one is the right tool | Concern | `APIRequestFactory` + direct call | `APIClient` | |---|---|---| | URL patterns, router names, trailing slashes | Not exercised | Exercised | | Project middleware | Skipped | Runs | | Authentication | `force_authenticate(request, ...)` or hand-set user | `force_authenticate()`, `credentials()`, `login()` | | Response bytes | Call `response.render()` first | Already rendered | | Viewset actions | Map them in `as_view({...})` | Router maps them | | Typical use | A view with branching logic, a custom `APIView` not yet routed, a reusable base view tested in isolation | Endpoint behaviour as a client sees it | ## Pitfalls that make factory tests lie - **Calling the action on an instance** — `ClaimViewSet().create(request)` skips `dispatch()`, so the request is never wrapped in DRF's `Request`, authentication and permissions never run, and the action receives a Django `HttpRequest` that has no `.data`. - **Putting the primary key only in the path** — `factory.get("/api/claims/7/")` does not give the view `pk=7`; only the call `view(request, pk=7)` does. - **Needing a DRF `Request` without running the view** — to unit-test `get_queryset()` or a helper that expects `request.query_params`, build one with `view_instance.initialize_request(factory_request)`, as DRF's testing guide shows, instead of assigning attributes to the raw request. - **Forgetting the default format** — `factory.post(path, data)` without `format="json"` is multipart, exactly as with the client. ## A practical rule Default to `APIClient`: most bugs in an API live in the wiring the factory skips — a view registered under the wrong basename, a middleware that rejects the request, a permission set in settings. Reach for the factory when the thing under test **is** the view class: a base view many endpoints inherit, a view whose logic branches heavily on the request, or one that is not in any URLconf yet. - Prefer the factory when a view is generic enough that routing it would mean inventing a URLconf just for the test; `URLPatternsTestCase` exists for the opposite case, when you do want routing for URLs that live only in the test module. - Keep factory tests close to the view's module, since they depend on its class name and action names. - Remember the factory cannot tell you that `reverse("claim-list")` works; one client-level test per endpoint still earns its place. - Both tools share `TEST_REQUEST_DEFAULT_FORMAT`, so a factory `post()` without `format="json"` is multipart too.
- Why does APIRequestFactory disable CSRF checks by default when Django's RequestFactory has no such option?Django's CSRF check lives in middleware, which never runs when a view is called directly. DRF wraps its views in `csrf_exempt` and re-runs the check inside `SessionAuthentication.authenticate()`, so it would fire for session-authenticated requests even in a factory test. The factory therefore marks requests to skip it unless built with `enforce_csrf_checks=True`.
- A factory test sets request.user = user but the view still treats the request as anonymous. Why?DRF builds its own `Request` when the view runs and asks the view's authentication classes for the user. Only `SessionAuthentication` reads the underlying `request.user`; with token authentication the hand-set attribute is ignored. `force_authenticate(request, user=user)` works for any configuration because it installs a forced authenticator on the DRF request.
saying these in an interview costs you the question
- Believing APIRequestFactory runs the URLconf and middleware like the client
- Calling a viewset's as_view() without an action mapping
- Reading response.content from a directly called view without rendering it
- Setting request.token on a factory request and expecting authentication
- Using the factory for every endpoint test and never testing the routes