Generate Bulk Test Data¶
Factories make it easy to seed development databases or create fixtures for end-to-end tests.
Step 1: Define factories¶
Factories are automatically generated based on the manager's interface. But you can customise generation by defining a nested Factory class.
from datetime import date
from general_manager.factory import (
lazy_measurement,
lazy_delta_date,
lazy_project_name,
)
from general_manager.measurement import (
MeasurementField,
Measurement,
)
from general_manager.interface import DatabaseInterface
from general_manager.manager import GeneralManager
from django.db.models import (
CharField,
DateField,
)
from typing import Optional
class Project(GeneralManager):
name: str
start_date: Optional[date]
end_date: Optional[date]
total_capex: Optional[Measurement]
class Interface(DatabaseInterface):
name = CharField(max_length=50)
start_date = DateField(null=True, blank=True)
end_date = DateField(null=True, blank=True)
total_capex = MeasurementField(base_unit="EUR", null=True, blank=True)
class Factory:
name = lazy_project_name()
end_date = lazy_delta_date(365 * 6, "start_date")
total_capex = lazy_measurement(75_000, 1_000_000, "EUR")
Step 2: Create batches¶
Project.Factory.create_batch(20)
InventoryItem.Factory.create_batch(50)
Many-to-many relations are populated automatically with sensible defaults.
Step 3: Customise values¶
Override attributes when calling the factory:
Project.Factory(name="Launch Project", start_date=date.today())
For complex scenarios, define @classmethod helpers that produce pre-wired object graphs (projects with members, factories with related measurements, etc.).
Use _adjustmentMethod for complex data creation¶
When a single factory call needs to fan out into multiple records, or when the final payload depends on derived values, define Factory._adjustmentMethod.
_adjustmentMethod receives the keyword arguments passed to the factory after relation values have been normalized. It must return either:
- one
dict[str, Any]for a single record - a
list[dict[str, Any]]for multiple records
Factory.create(...) validates and saves every returned record, then wraps the saved models back into their GeneralManager class. Factory.build(...) runs the same adjustment logic but returns unsaved model instances.
Example:
from typing import Any
from django.db.models import CharField, PositiveIntegerField
from general_manager.interface import DatabaseInterface
from general_manager.manager import GeneralManager
class Fleet(GeneralManager):
label: str
capacity: int
class Interface(DatabaseInterface):
label = CharField(max_length=64)
capacity = PositiveIntegerField()
class Factory:
@staticmethod
def _adjustmentMethod(
*,
label: str = "Fleet",
capacity: int = 0,
count: int = 1,
**extra: Any,
) -> list[dict[str, Any]]:
records: list[dict[str, Any]] = []
for index in range(count):
record = {
"label": f"{label}-{index}",
"capacity": capacity + index,
}
if "changed_by" in extra:
record["changed_by"] = extra["changed_by"]
records.append(record)
return records
fleets = Fleet.Factory.create(label="North", capacity=10, count=3)
assert [fleet.label for fleet in fleets] == [
"North-0",
"North-1",
"North-2",
]
assert [fleet.capacity for fleet in fleets] == [10, 11, 12]
Use this hook when the number of records is dynamic, when values need to be generated from a shared seed, or when the created objects must stay internally consistent. Keep _adjustmentMethod focused on shaping record dictionaries; validation still happens later through the normal model full_clean() and save flow.
Step 4: Integrate with pytest fixtures¶
@pytest.fixture
def project_factory() -> Callable[..., Project]:
def _factory(**kwargs):
return Project.Factory(**kwargs)
return _factory
Use the fixture in tests to create data on demand.
Step 5: Tear down¶
Use database transactions or pytest's django_db(reset_sequences=True) marker to keep the test environment clean after bulk creation.