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 FakerBasic 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 = 0Using 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_publishedSequences
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 >= 1000Batch 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-bakeryBasic 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) == 10Related Objects
@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_publishedfactory_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) == 5Avoiding 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.Sequencefor unique string fields (usernames, slugs) - Use
factory.SubFactoryfor required ForeignKey fields - Use traits for common states (published, popular, draft, deleted)
- Use
create_batchfor pagination and list tests - Use model_bakery for quick, no-setup test data creation
- Define shared factories in
conftest.pyortests/factories.py - Reset sequences in
tearDownif 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.