Permalink
Fetching contributors…
Cannot retrieve contributors at this time
499 lines (332 sloc) 15.3 KB

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 :class:`~django.db.models.ForeignKey` to another :class:`~django.db.models.Model`), use the :class:`~factory.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 :class:`~django.db.models.ForeignKey` should be chosen from a pre-populated table (e.g :class:`django.contrib.contenttypes.models.ContentType`), simply use a :class:`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 :class:`~django.db.models.ForeignKey` from another :class:`~django.db.models.Model`), use a :class:`~factory.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 :class:`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 :class:`~django.db.models.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 :meth:`~factory.Factory._generate` method on the factory.

Since version 2.9, the :meth:`~factory.django.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 :class:`~factory.SubFactory` will also need to be decorated in the same manner.

Note

When any :class:`~factory.RelatedFactory` or :class:`~factory.post_generation` attribute is defined on the :class:`~factory.django.DjangoModelFactory` subclass, a second save() is performed after the call to _create().

Code working with signals should thus use the :meth:`~factory.django.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 :class:`~factory.SubFactory` or :class:`~factory.RelatedFactory`, users will have to craft their own depending on the model.

The base building block for this feature is the :class:`~factory.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 :class:`RelatedFactory`. If more links are needed, simply add more :class:`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:

  1. UserWithGroupFactory() generates a User instance, obj
  2. It calls GroupLevelFactory(user=obj)
  3. It returns obj

When using the UserWith2GroupsFactory, that behavior becomes:

  1. UserWith2GroupsFactory() generates a User instance, obj
  2. It calls GroupLevelFactory(user=obj, group__name='Group1')
  3. It calls GroupLevelFactory(user=obj, group__name='Group2')
  4. 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 :meth:`Model.objects.create() <django.db.models.query.QuerySet.create>` method:

class UserFactory(factory.django.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 :class:`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 :class:`~factory.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 :meth:`factory.Factory.reset_sequence`; this will reset the counter to its initial value (as defined by :meth:`factory.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 :class:`~unittest.TestCase`'s :meth:`~unittest.TestCase.setUp` method.

Forcing the initial value for all projects

The sequence counter of a :class:`~factory.Factory` can also be set automatically upon the first call through the :meth:`~factory.Factory._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:

  1. Merge declarations and overrides from all sources (class definition, call parameters, ...)
  2. Resolve them into a dict
  3. 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 :meth:`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

.. literalinclude:: ../examples/django_demo/generic_foreignkey/models.py

We can create factories like this:

.. literalinclude:: ../examples/django_demo/generic_foreignkey/factories.py