Preparing test data#
The arrange phase prepares the input and application state required by a test. Good test data makes the scenario obvious: values that affect the behavior are explicit, while incidental values require as little maintenance as possible.
Choose the simplest preparation strategy that communicates the test’s intent:
Strategy |
Use it when |
Benefit |
|---|---|---|
Explicit values |
A particular value is part of the scenario. |
The important conditions are visible directly in the test. |
Fixtures |
Several tests need the same object or application state. |
Reusable setup stays separate while dependencies remain visible in the test signature. |
Faker |
A field needs a valid value, but its exact value is irrelevant. |
Tests can exercise a wider range of inputs without hand-written samples. |
Model factories |
A complete structured object would be tedious to construct manually. |
Valid data is generated from the same model that defines the API contract. |
Tip
Generated data is not automatically better than explicit data. Keep values that explain the scenario fixed, and generate only the details that are not relevant to the behavior under test. When choosing a model factory, consult the full Polyfactory usage documentation for all available features.
Start with explicit values#
Prefer a small dictionary or model instance when all relevant fields fit comfortably in the test. This makes the arrange phase readable without jumping to a fixture or factory definition.
1import json
2from http import HTTPStatus
3
4from dirty_equals import IsUUID
5from django.http import HttpResponse
6
7from dmr.test import DMRRequestFactory
8from examples.testing.pydantic_controller import UserController
9
10
11def test_request_factory(dmr_rf: DMRRequestFactory) -> None:
12 request_data = {'email': 'test@example.com', 'age': 43}
13 request = dmr_rf.post('/users/', data=request_data)
14
15 response = UserController.as_view()(request)
16
17 assert isinstance(response, HttpResponse)
18 assert request.content_type == 'application/json'
19 assert response.status_code == HTTPStatus.CREATED
20 assert response.headers == {'Content-Type': 'application/json'}
21 assert json.loads(response.content) == {
22 'uid': IsUUID,
23 **request_data,
24 }
In this example, the email and age describe the exact request being tested. There is no benefit in generating them because the setup is already short and clear.
Generate structured data from models#
As request models grow, keeping every valid field in each test becomes noisy
and makes schema changes expensive. Polyfactory can build data from
pydantic, msgspec, @dataclass, and TypedDict models.
Suppose the controller uses these pydantic models:
1import uuid
2
3import pydantic
4
5from dmr import Body, Controller
6from dmr.plugins.pydantic import PydanticFastSerializer
7
8
9class UserCreateModel(pydantic.BaseModel):
10 email: str
11 age: int
12
13
14class UserModel(UserCreateModel):
15 uid: uuid.UUID
16
17
18class UserController(Controller[PydanticFastSerializer]):
19 def post(self, parsed_body: Body[UserCreateModel]) -> UserModel:
20 return UserModel(
21 uid=uuid.uuid4(),
22 age=parsed_body.age,
23 email=parsed_body.email,
24 )
The same request model can define the factory used by the test:
1import json
2from http import HTTPStatus
3
4from dirty_equals import IsUUID
5from django.http import HttpResponse
6from polyfactory.factories.pydantic_factory import ModelFactory
7
8from dmr.test import DMRRequestFactory
9from examples.testing.pydantic_controller import UserController, UserCreateModel
10
11
12class UserCreateModelFactory(ModelFactory[UserCreateModel]):
13 """Will create structured random request data for you."""
14
15 __check_model__ = True
16
17
18def test_create_user(dmr_rf: DMRRequestFactory) -> None:
19 # This will return random `UserCreatedModel` instances:
20 request_data = UserCreateModelFactory.build().model_dump(mode='json')
21
22 request = dmr_rf.post('/url/', data=request_data)
23
24 response = UserController.as_view()(request)
25
26 assert isinstance(response, HttpResponse)
27 assert response.status_code == HTTPStatus.CREATED
28 assert response.headers == {'Content-Type': 'application/json'}
29 assert json.loads(response.content) == {
30 'uid': IsUUID,
31 **request_data,
32 }
The important parts are:
Choose the factory base that matches the model type.
Set
__check_model__ = Trueso generated values are validated against the model.Use
build()when you need a model instance without persisting anything.Override fields that define the scenario and let the factory fill in the incidental fields.
Serialize the resulting model in the same representation accepted by the API.
Tip
This example intentionally covers only the basic workflow. See the complete Polyfactory usage documentation for supported model types, field customization, constraints, nested factories, persistence handlers, and other available features.
Keep the arrange phase focused#
Test setup should contain only what is needed to reach the behavior under test:
Make values involved in the expected behavior explicit.
Generate valid values for fields whose exact contents do not matter.
Use fixtures for genuinely shared setup or application state, not merely to hide a few local values.
Override generated values at the test site when they explain why the case is interesting.
Avoid assertions that depend on a specific random value. Assert the stable result or describe dynamic output with an appropriate matcher instead.
After arranging the input and executing the behavior, see Test assertions for choosing between exact snapshots and matchers for dynamic values.