Faster factory_boy and model_bakery factories
Slow factory_boy and model_bakery factories? Create shared data once, use build() or baker.prepare() instead of saving, bulk create rows, reuse one Faker instance and tune Hypothesis.
Factories make test data cheap to write: OrderFactory() gives you an order with a customer, a user, an address and three line items. That convenience hides how much work happens. Each of those objects is an INSERT, some of them run save() twice, and the user may hash a password on the way.
Called once that's fine. Called in setUp for every test, or 200 times in a loop, factories easily become the largest share of a suite's time, most of it spent writing rows the test never looks at.
Why factories are slow
- One query per object.
create()saves each object separately.create_batch(500)is 500create()calls, so 500INSERTstatements. - Hidden related objects. A
SubFactorycreates its related object too, and that one may have its ownSubFactory.RelatedFactorycreates objects after the parent. One call can write a dozen rows. - Extra saves. In factory_boy,
DjangoModelFactorysaves the instance again afterpost_generationhooks run. - Fake data. Faker generates every field value in Python, and creating a
Faker()instance loads its locale providers, which is slow if you do it per call. - Expensive side effects. A factory that calls
set_passwordhashes a password every time (see use a fast password hasher).
Create shared data once
The biggest win usually isn't in the factory: it's calling it less. Objects that tests only read should be built once per class with setUpTestData (or once per module or session with a pytest fixture):
from django.test import TestCase
from model_bakery import baker
from shop.models import Order
class TestOrderReport(TestCase):
@classmethod
def setUpTestData(cls):
cls.customer = baker.make("shop.Customer")
cls.orders = baker.make(Order, customer=cls.customer, _quantity=20)
def test_total(self):
self.assertEqual(self.customer.orders.count(), 20)
See setUpTestData instead of setUp for the details.
Don't save what doesn't need saving
Tests of methods, serializers, forms and validation often don't need rows in the database at all. Build the objects in memory instead.
factory_boy:
user = UserFactory.build() # a model instance, not saved
order = OrderFactory.build() # its SubFactory objects are built, not saved, too
data = UserFactory.stub() # a plain object with the attributes, no model at all
users = UserFactory.build_batch(10)
model_bakery:
from model_bakery import baker
order = baker.prepare("shop.Order") # not saved, related objects not saved either
order = baker.prepare("shop.Order", _save_related=True) # saves the customer, not the order
orders = baker.prepare("shop.Order", _quantity=10)
Unsaved objects have no primary key, so code that queries the database won't find them, and Django raises an error if you use a reverse relation or many-to-many on them. Keep create() / baker.make() for those tests.
Create many rows in one query
When a test needs a lot of rows (pagination, reports, bulk actions), insert them with one bulk_create instead of one INSERT each.
model_bakery has it built in:
customer = baker.make("shop.Customer")
baker.make("shop.Order", customer=customer, _quantity=500, _bulk_create=True)
Pass shared foreign keys explicitly as above. Otherwise model_bakery saves a related object for every row before the bulk insert.
factory_boy has no bulk option, but build_batch plus Django's bulk_create does the same:
customer = CustomerFactory()
orders = Order.objects.bulk_create(OrderFactory.build_batch(500, customer=customer))
bulk_create doesn't call save() or send pre_save/post_save signals. On PostgreSQL, MariaDB and SQLite 3.35+ the returned objects get their primary keys set.
Trim the factories
- Pass related objects in instead of letting each call create its own:
OrderFactory(customer=customer). - Make expensive relations opt-in with factory_boy
Traits instead of creating them by default. - If a
post_generationhook doesn't change fields that need saving, setskip_postgeneration_save = Truein the factory'sMetato drop the extrasave(). Recent factory_boy versions warn about this save.
import factory
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = "auth.User"
skip_postgeneration_save = True
username = factory.Sequence(lambda n: f"user{n}")
Reuse one Faker instance
Creating Faker() is much more expensive than generating a value. Create it once at module level:
from faker import Faker
fake = Faker()
def make_address():
return {"street": fake.street_address(), "city": fake.city()}
In pytest tests, use Faker's faker fixture: it returns a session-scoped instance and reseeds it (with 0 by default) for every test. factory.Faker declarations in factory_boy already share instances.
Fewer Hypothesis examples locally
Hypothesis runs 100 examples per test by default. Register a lighter profile for local runs and keep the default in CI:
# conftest.py
from hypothesis import settings
settings.register_profile("dev", max_examples=10)
$ pytest --hypothesis-profile=dev
Or call settings.load_profile("dev") when an environment variable you set locally is present. Hypothesis activates its built-in ci profile automatically when the CI environment variable is set, so don't load a profile unconditionally or CI loses it.
Keep the tests correct
- Shared objects must not be modified by tests, or must live inside
TestCase's rollback (data fromsetUpTestDatadoes). bulk_createskipssave()and signals. If a test relies on what they do (computed fields, audit rows), create those rows normally.- Fewer Hypothesis examples locally find fewer bugs locally. CI keeps the full count.
How flakytest finds it
flakytest puts time in factory_boy, model_bakery, Faker, mixer and Hypothesis into a test data factories category, and also counts the database writes that factories trigger. When the two add up to at least 1 second and 3% of test time, it suggests Test data factories are a large share of test time, with the lines that call the factories. It adds the Faker advice when creating Faker() instances costs 0.2 seconds or more, and the Hypothesis advice when Hypothesis takes a second or more.
Further reading
- factory_boy reference: build, create, stub and batches
- model_bakery: basic usage
- Django:
bulk_create() - Faker: pytest fixtures
- Hypothesis: settings profiles
Related guides
- Create shared test data in setUpTestData instead of setUp: Django runs setUp before every test, so its rows are inserted again each time. setUpTestData creates them once per TestCase class, and every test still starts clean.
- Expensive function-scoped pytest fixtures: A pytest fixture without a scope runs again for every test that uses it. Give expensive fixtures module or session scope, or split them into a shared part and a cheap reset.
- Use a fast password hasher in tests: Django hashes passwords with hundreds of thousands of PBKDF2 iterations, and every create_user and client.login in a test pays for it. Switch to a fast hasher in test settings and use force_login.
- Django testing best practices: How to write fast, reliable Django tests: TestCase vs TransactionTestCase, setUpTestData, model_bakery, force_login, test settings, assertNumQueries and parallel runs.
Updated October 18, 2026 · Suggest a change