Common recipes
Note
Most recipes below take on Django model examples, but can also be used on their own.
Dependent objects (ForeignKey)
When one attribute is actually a complex field
(e.g a ForeignKey
to another Model
),
use the SubFactory
declaration:
# models.py
class User(models.Model):
first_name = models.CharField()
group = models.ForeignKey(Group)
# factories.py
import factory
from . import models
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.User
first_name = factory.Sequence(lambda n: "Agent %03d" % n)
group = factory.SubFactory(GroupFactory)
Choosing from a populated table
If the target of the ForeignKey
should be
chosen from a pre-populated table
(e.g django.contrib.contenttypes.models.ContentType
),
simply use a factory.Iterator
on the chosen queryset:
import factory, factory.django
from . import models
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.User
language = factory.Iterator(models.Language.objects.all())
Here, models.Language.objects.all()
won’t be evaluated until the
first call to UserFactory
; thus avoiding DB queries at import time.
Reverse dependencies (reverse ForeignKey)
When a related object should be created upon object creation
(e.g a reverse ForeignKey
from another Model
),
use a RelatedFactory
declaration:
# models.py
class User(models.Model):
pass
class UserLog(models.Model):
user = models.ForeignKey(User)
action = models.CharField()
# factories.py
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.User
log = factory.RelatedFactory(UserLogFactory, 'user', action=models.UserLog.ACTION_CREATE)
When a UserFactory
is instantiated, factory_boy will call
UserLogFactory(user=that_user, action=...)
just before returning the created User
.
Example: Django’s Profile
Django (<1.5) provided a mechanism to attach a Profile
to a User
instance,
using a OneToOneField
from the Profile
to the User
.
A typical way to create those profiles was to hook a post-save signal to the User
model.
Prior to version 2.9, the solution to this was to override the _generate()
method on the factory.
Since version 2.9, the mute_signals()
decorator should be used:
@factory.django.mute_signals(post_save)
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = my_models.Profile
title = 'Dr'
# We pass in profile=None to prevent UserFactory from creating another profile
# (this disables the RelatedFactory)
user = factory.SubFactory('app.factories.UserFactory', profile=None)
@factory.django.mute_signals(post_save)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = auth_models.User
username = factory.Sequence(lambda n: "user_%d" % n)
# We pass in 'user' to link the generated Profile to our just-generated User
# This will call ProfileFactory(user=our_new_user), thus skipping the SubFactory.
profile = factory.RelatedFactory(ProfileFactory, 'user')
>>> u = UserFactory(profile__title=u"Lord")
>>> u.get_profile().title
u"Lord"
Such behaviour can be extended to other situations where a signal interferes with factory_boy related factories.
Any factories that call these classes with SubFactory
will also need to be decorated in the same manner.
Note
When any RelatedFactory
or post_generation
attribute is defined on the DjangoModelFactory
subclass,
a second save()
is performed after the call to _create()
.
Code working with signals should thus use the mute_signals()
decorator
Simple Many-to-many relationship
Building the adequate link between two models depends heavily on the use case;
factory_boy doesn’t provide a “all in one tools” as for SubFactory
or RelatedFactory
, users will have to craft their own depending
on the model.
The base building block for this feature is the post_generation
hook:
# models.py
class Group(models.Model):
name = models.CharField()
class User(models.Model):
name = models.CharField()
groups = models.ManyToManyField(Group)
# factories.py
class GroupFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Group
name = factory.Sequence(lambda n: "Group #%s" % n)
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.User
name = "John Doe"
@factory.post_generation
def groups(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of groups were passed in, use them
for group in extracted:
self.groups.add(group)
When calling UserFactory()
or UserFactory.build()
, no group binding
will be created.
But when UserFactory.create(groups=(group1, group2, group3))
is called,
the groups
declaration will add passed in groups to the set of groups for the
user.
Many-to-many relation with a ‘through’
If only one link is required, this can be simply performed with a RelatedFactory
.
If more links are needed, simply add more RelatedFactory
declarations:
# models.py
class User(models.Model):
name = models.CharField()
class Group(models.Model):
name = models.CharField()
members = models.ManyToManyField(User, through='GroupLevel')
class GroupLevel(models.Model):
user = models.ForeignKey(User)
group = models.ForeignKey(Group)
rank = models.IntegerField()
# factories.py
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.User
name = "John Doe"
class GroupFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Group
name = "Admins"
class GroupLevelFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.GroupLevel
user = factory.SubFactory(UserFactory)
group = factory.SubFactory(GroupFactory)
rank = 1
class UserWithGroupFactory(UserFactory):
membership = factory.RelatedFactory(GroupLevelFactory, 'user')
class UserWith2GroupsFactory(UserFactory):
membership1 = factory.RelatedFactory(GroupLevelFactory, 'user', group__name='Group1')
membership2 = factory.RelatedFactory(GroupLevelFactory, 'user', group__name='Group2')
Whenever the UserWithGroupFactory
is called, it will, as a post-generation hook,
call the GroupLevelFactory
, passing the generated user as a user
field:
UserWithGroupFactory()
generates aUser
instance,obj
It calls
GroupLevelFactory(user=obj)
It returns
obj
When using the UserWith2GroupsFactory
, that behavior becomes:
UserWith2GroupsFactory()
generates aUser
instance,obj
It calls
GroupLevelFactory(user=obj, group__name='Group1')
It calls
GroupLevelFactory(user=obj, group__name='Group2')
It returns
obj
Copying fields to a SubFactory
When a field of a related class should match one of the container:
# models.py
class Country(models.Model):
name = models.CharField()
lang = models.CharField()
class User(models.Model):
name = models.CharField()
lang = models.CharField()
country = models.ForeignKey(Country)
class Company(models.Model):
name = models.CharField()
owner = models.ForeignKey(User)
country = models.ForeignKey(Country)
Here, we want:
The User to have the lang of its country (
factory.SelfAttribute('country.lang')
)The Company owner to live in the country of the company (
factory.SelfAttribute('..country')
)
# factories.py
class CountryFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Country
name = factory.Iterator(["France", "Italy", "Spain"])
lang = factory.Iterator(['fr', 'it', 'es'])
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.User
name = "John"
lang = factory.SelfAttribute('country.lang')
country = factory.SubFactory(CountryFactory)
class CompanyFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Company
name = "ACME, Inc."
country = factory.SubFactory(CountryFactory)
owner = factory.SubFactory(UserFactory, country=factory.SelfAttribute('..country'))
If the value of a field on the child factory is indirectly derived from a field on the parent factory, you will need to use LazyAttribute and poke the “factory_parent” attribute.
This time, we want the company owner to live in a country neighboring the country of the company:
class CompanyFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Company
name = "ACME, Inc."
country = factory.SubFactory(CountryFactory)
owner = factory.SubFactory(UserFactory,
country=factory.LazyAttribute(lambda o: get_random_neighbor(o.factory_parent.country)))
Custom manager methods
Sometimes you need a factory to call a specific manager method other then the
default Model.objects.create()
method:
class UserFactory(factory.DjangoModelFactory):
class Meta:
model = UserenaSignup
username = "l7d8s"
email = "my_name@example.com"
password = "my_password"
@classmethod
def _create(cls, model_class, *args, **kwargs):
"""Override the default ``_create`` with our custom call."""
manager = cls._get_manager(model_class)
# The default would use ``manager.create(*args, **kwargs)``
return manager.create_user(*args, **kwargs)
Forcing the sequence counter
A common pattern with factory_boy is to use a factory.Sequence
declaration
to provide varying values to attributes declared as unique.
However, it is sometimes useful to force a given value to the counter, for instance to ensure that tests are properly reproductible.
factory_boy provides a few hooks for this:
- Forcing the value on a per-call basis
In order to force the counter for a specific
Factory
instantiation, just pass the value in the__sequence=42
parameter:class AccountFactory(factory.Factory): class Meta: model = Account uid = factory.Sequence(lambda n: n) name = "Test"
>>> obj1 = AccountFactory(name="John Doe", __sequence=10) >>> obj1.uid # Taken from the __sequence counter 10 >>> obj2 = AccountFactory(name="Jane Doe") >>> obj2.uid # The base sequence counter hasn't changed 1
- Resetting the counter globally
If all calls for a factory must start from a deterministic number, use
factory.Factory.reset_sequence()
; this will reset the counter to its initial value (as defined byfactory.Factory._setup_next_sequence()
).>>> AccountFactory().uid 1 >>> AccountFactory().uid 2 >>> AccountFactory.reset_sequence() >>> AccountFactory().uid # Reset to the initial value 1 >>> AccountFactory().uid 2
It is also possible to reset the counter to a specific value:
>>> AccountFactory.reset_sequence(10) >>> AccountFactory().uid 10 >>> AccountFactory().uid 11
This recipe is most useful in a
TestCase
’ssetUp()
method.- Forcing the initial value for all projects
The sequence counter of a
Factory
can also be set automatically upon the first call through the_setup_next_sequence()
method; this helps when the objects’s attributes mustn’t conflict with pre-existing data.A typical example is to ensure that running a Python script twice will create non-conflicting objects, by setting up the counter to “max used value plus one”:
class AccountFactory(factory.django.DjangoModelFactory): class Meta: model = models.Account @classmethod def _setup_next_sequence(cls): try: return models.Accounts.objects.latest('uid').uid + 1 except models.Account.DoesNotExist: return 1
>>> Account.objects.create(uid=42, name="Blah") >>> AccountFactory.create() # Sets up the account number based on the latest uid <Account uid=43, name=Test>
Converting a factory’s output to a dict
In order to inject some data to, say, a REST API, it can be useful to fetch the factory’s data as a dict.
Internally, a factory will:
Merge declarations and overrides from all sources (class definition, call parameters, …)
Resolve them into a dict
Pass that dict as keyword arguments to the model’s
build
/create
function
In order to get a dict, we’ll just have to swap the model; the easiest way is to use
factory.build()
:
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.User
first_name = factory.Sequence(lambda n: "Agent %03d" % n)
username = factory.Faker('username')
>>> factory.build(dict, FACTORY_CLASS=UserFactory)
{'first_name': "Agent 001", 'username': 'john_doe'}
Django models with GenericForeignKeys
For model which uses GenericForeignKey
from __future__ import unicode_literals
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
class TaggedItem(models.Model):
"""Example GenericForeinKey model from django docs"""
tag = models.SlugField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
def __str__(self): # __unicode__ on Python 2
return self.tag
We can create factories like this:
import factory
from django.contrib.auth.models import User, Group
from django.contrib.contenttypes.models import ContentType
from .models import TaggedItem
class UserFactory(factory.DjangoModelFactory):
first_name = 'Adam'
class Meta:
model = User
class GroupFactory(factory.DjangoModelFactory):
name = 'group'
class Meta:
model = Group
class TaggedItemFactory(factory.DjangoModelFactory):
object_id = factory.SelfAttribute('content_object.id')
content_type = factory.LazyAttribute(
lambda o: ContentType.objects.get_for_model(o.content_object))
class Meta:
exclude = ['content_object']
abstract = True
class TaggedUserFactory(TaggedItemFactory):
content_object = factory.SubFactory(UserFactory)
class Meta:
model = TaggedItem
class TaggedGroupFactory(TaggedItemFactory):
content_object = factory.SubFactory(GroupFactory)
class Meta:
model = TaggedItem