# 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 500 `create()` calls, so 500 `INSERT` statements.
- **Hidden related objects.** A `SubFactory` creates its related object too, and that one may have its own `SubFactory`. `RelatedFactory` creates objects after the parent. One call can write a dozen rows.
- **Extra saves.** In factory_boy, `DjangoModelFactory` saves the instance again after `post_generation` hooks 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_password` hashes a password every time (see [use a fast password hasher](https://flakytest.dev/guides/fast-password-hasher-in-tests/)).

## 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):

```python
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](https://flakytest.dev/guides/setuptestdata-vs-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:

```python
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:

```python
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:

```python
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:

```python
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 `Trait`s instead of creating them by default.
- If a `post_generation` hook doesn't change fields that need saving, set `skip_postgeneration_save = True` in the factory's `Meta` to drop the extra `save()`. Recent factory_boy versions warn about this save.

```python
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:

```python
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:

```python
# conftest.py
from hypothesis import settings

settings.register_profile("dev", max_examples=10)
```

```console
$ 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 from `setUpTestData` does).
- `bulk_create` skips `save()` 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](https://factoryboy.readthedocs.io/en/stable/reference.html)
- [model_bakery: basic usage](https://model-bakery.readthedocs.io/en/latest/basic_usage.html)
- [Django: `bulk_create()`](https://docs.djangoproject.com/en/stable/ref/models/querysets/#bulk-create)
- [Faker: pytest fixtures](https://faker.readthedocs.io/en/master/pytest-fixtures.html)
- [Hypothesis: settings profiles](https://hypothesis.readthedocs.io/en/latest/reference/api.html#hypothesis.settings.register_profile)
