factory_boy and model_bakery for Django Test Data: Complete Guide

factory_boy and model_bakery for Django Test Data: Complete Guide

Creating test data manually with Model.objects.create(...) doesn't scale. When models have 10+ fields, required foreign keys, and complex relationships, tests become maintenance nightmares. factory_boy and model_bakery solve this with generated test data that respects your model's structure.

factory_boy

factory_boy generates model instances with sensible defaults. You only specify what matters for each test.

Installation

pip install factory-boy Faker

Basic Factory

# tests/factories.py
import factory
from factory.django import DjangoModelFactory
from django.contrib.auth import get_user_model
from myapp.models import Article, Category, Tag

User = get_user_model()


class UserFactory(DjangoModelFactory):
    class Meta:
        model = User

    username = factory.Sequence(lambda n: f'user{n}')
    email = factory.LazyAttribute(lambda obj: f'{obj.username}@example.com')
    password = factory.PostGenerationMethodCall('set_password', 'testpass')
    is_active = True


class CategoryFactory(DjangoModelFactory):
    class Meta:
        model = Category

    name = factory.Sequence(lambda n: f'Category {n}')
    slug = factory.LazyAttribute(lambda obj: obj.name.lower().replace(' ', '-'))


class ArticleFactory(DjangoModelFactory):
    class Meta:
        model = Article

    title = factory.Faker('sentence', nb_words=5)
    content = factory.Faker('paragraphs', nb=3, as_text=True)
    author = factory.SubFactory(UserFactory)
    category = factory.SubFactory(CategoryFactory)
    published_at = None  # Draft by default
    view_count = 0

Using Factories in Tests

# tests/test_articles.py
import pytest
from tests.factories import UserFactory, ArticleFactory, CategoryFactory


@pytest.mark.django_db
def test_article_creation():
    article = ArticleFactory()

    assert article.pk is not None
    assert article.title  # Faker-generated
    assert article.author.pk is not None  # SubFactory created user too


@pytest.mark.django_db
def test_article_with_specific_values():
    user = UserFactory(username='alice')
    article = ArticleFactory(title='My Specific Title', author=user)

    assert article.title == 'My Specific Title'
    assert article.author.username == 'alice'


@pytest.mark.django_db
def test_published_article():
    from django.utils import timezone
    article = ArticleFactory(published_at=timezone.now())
    assert article.is_published

Sequences

Sequences generate unique values per factory call:

class UserFactory(DjangoModelFactory):
    username = factory.Sequence(lambda n: f'user{n}')
    # user0, user1, user2, ...

class ArticleFactory(DjangoModelFactory):
    slug = factory.Sequence(lambda n: f'article-{n}')
    # article-0, article-1, article-2, ...

Lazy Attributes

Lazy attributes compute values based on other fields:

class ArticleFactory(DjangoModelFactory):
    title = factory.Faker('sentence', nb_words=4)
    slug = factory.LazyAttribute(
        lambda obj: obj.title.lower().replace(' ', '-').replace('.', '')
    )
    meta_title = factory.LazyAttribute(
        lambda obj: f'{obj.title} | My Site'[:60]
    )

Traits

Traits group related overrides under a name:

class ArticleFactory(DjangoModelFactory):
    class Meta:
        model = Article

    title = factory.Faker('sentence', nb_words=5)
    content = factory.Faker('text')
    author = factory.SubFactory(UserFactory)
    published_at = None
    view_count = 0

    class Params:
        published = factory.Trait(
            published_at=factory.LazyFunction(
                lambda: __import__('django.utils.timezone', fromlist=['timezone']).timezone.now()
            )
        )
        popular = factory.Trait(view_count=factory.Faker('random_int', min=1000, max=9999))
        featured = factory.Trait(
            published_at=factory.LazyFunction(
                lambda: __import__('django.utils.timezone', fromlist=['timezone']).timezone.now()
            ),
            view_count=5000,
        )

Usage:

@pytest.mark.django_db
def test_popular_published_articles():
    # Regular draft
    draft = ArticleFactory()
    assert draft.published_at is None

    # Published
    published = ArticleFactory(published=True)
    assert published.is_published

    # Popular published
    popular = ArticleFactory(published=True, popular=True)
    assert popular.view_count >= 1000

Batch Creation

@pytest.mark.django_db
def test_pagination_with_many_articles():
    author = UserFactory()
    articles = ArticleFactory.create_batch(25, author=author, published=True)

    assert len(articles) == 25
    assert all(a.author == author for a in articles)

Many-to-Many Relationships

class TagFactory(DjangoModelFactory):
    class Meta:
        model = Tag

    name = factory.Sequence(lambda n: f'tag-{n}')


class ArticleFactory(DjangoModelFactory):
    class Meta:
        model = Article

    @factory.post_generation
    def tags(self, create, extracted, **kwargs):
        if not create:
            return
        if extracted:
            self.tags.set(extracted)
        else:
            # Default: add 2 random tags
            self.tags.set(TagFactory.create_batch(2))
@pytest.mark.django_db
def test_article_with_specific_tags():
    python_tag = TagFactory(name='Python')
    django_tag = TagFactory(name='Django')
    article = ArticleFactory(tags=[python_tag, django_tag])

    assert article.tags.count() == 2
    assert python_tag in article.tags.all()

Django-specific Factories

from factory.django import ImageField, FileField


class UserProfileFactory(DjangoModelFactory):
    class Meta:
        model = UserProfile

    user = factory.SubFactory(UserFactory)
    avatar = ImageField(color='blue')  # Generates a real image file
    resume = FileField(data=b'PDF content here')
    bio = factory.Faker('text', max_nb_chars=200)

model_bakery

model_bakery takes a different approach — it introspects your models and fills all required fields automatically. No factory definition needed for simple cases:

Installation

pip install model-bakery

Basic Usage

from model_bakery import baker


@pytest.mark.django_db
def test_with_baker():
    # Creates Article with all required fields filled automatically
    article = baker.make('myapp.Article')
    assert article.pk is not None
    assert article.title  # Auto-generated


@pytest.mark.django_db
def test_override_specific_fields():
    article = baker.make('myapp.Article', title='My Title', view_count=100)
    assert article.title == 'My Title'
    assert article.view_count == 100


@pytest.mark.django_db
def test_baker_batch():
    articles = baker.make('myapp.Article', _quantity=10)
    assert len(articles) == 10
@pytest.mark.django_db
def test_baker_handles_foreign_keys():
    # baker creates the author automatically
    article = baker.make('myapp.Article')
    assert article.author is not None
    assert article.author.pk is not None


@pytest.mark.django_db
def test_specify_related_object():
    user = baker.make('auth.User', username='alice')
    article = baker.make('myapp.Article', author=user)
    assert article.author.username == 'alice'

Prepare (No DB)

baker.prepare creates objects in memory without saving:

def test_model_validation_no_db():
    article = baker.prepare('myapp.Article', title='')

    from django.core.exceptions import ValidationError
    with pytest.raises(ValidationError):
        article.full_clean()

Custom Recipes

Recipes are named, reusable baker configurations — similar to factory_boy traits:

# myapp/baker_recipes.py
from model_bakery.recipe import Recipe, foreign_key
from django.utils import timezone

published_article = Recipe(
    'myapp.Article',
    published_at=timezone.now,
    view_count=100,
)

popular_article = Recipe(
    'myapp.Article',
    published_at=timezone.now,
    view_count=5000,
)
from model_bakery.recipe import baker

@pytest.mark.django_db
def test_popular_article_recipe():
    article = baker.make_recipe('myapp.popular_article')
    assert article.view_count == 5000
    assert article.is_published

factory_boy vs model_bakery

Feature factory_boy model_bakery
Setup required Define factory classes None
Customization Traits, sequences, lazy attrs Recipes
Introspection No Yes — fills all fields
Faker integration Native Via baker.make(..., _fill_optional=True)
Signals Can disable baker.make(..., _signal_processor=None)
Best for Complex models, precise control Simple cases, quick tests

Fixtures with Factories

Combine pytest fixtures with factories for reusable, composable test data:

# conftest.py
import pytest
from tests.factories import UserFactory, ArticleFactory


@pytest.fixture
def user(db):
    return UserFactory()


@pytest.fixture
def admin(db):
    return UserFactory(is_staff=True, is_superuser=True)


@pytest.fixture
def article(db, user):
    return ArticleFactory(author=user)


@pytest.fixture
def published_articles(db, user):
    return ArticleFactory.create_batch(5, author=user, published=True)
@pytest.mark.django_db
def test_admin_can_see_all_articles(client, admin, published_articles):
    client.force_login(admin)
    response = client.get('/admin/myapp/article/')
    assert response.status_code == 200
    # All 5 articles visible to admin
    assert len(published_articles) == 5

Avoiding Common Pitfalls

# BAD: Creates a new user for EVERY call — causes unique constraint errors
class ArticleFactory(DjangoModelFactory):
    author = UserFactory()  # Evaluated at class definition time!

# GOOD: SubFactory creates a new user per factory call
class ArticleFactory(DjangoModelFactory):
    author = factory.SubFactory(UserFactory)  # Called each time

# BAD: Sharing mutable state between tests
articles = ArticleFactory.create_batch(5)  # Module-level — not reset between tests

# GOOD: Create in fixtures or test functions
@pytest.fixture
def articles(db):
    return ArticleFactory.create_batch(5)

Key Patterns

  • Use factory.Sequence for unique string fields (usernames, slugs)
  • Use factory.SubFactory for required ForeignKey fields
  • Use traits for common states (published, popular, draft, deleted)
  • Use create_batch for pagination and list tests
  • Use model_bakery for quick, no-setup test data creation
  • Define shared factories in conftest.py or tests/factories.py
  • Reset sequences in tearDown if you have snapshot tests that depend on exact values

Factory-based test data is one of the highest-leverage investments in a Django test suite. Good factories eliminate boilerplate, make tests readable, and let you focus assertions on what actually matters.

Read more

Start now free