Skip to content

Factory boy

https://factoryboy.readthedocs.io/en/stable/index.html

As a fixtures replacement tool, it aims to replace static, hard to maintain fixtures with easy-to-use factories for complex objects.

This is a simple how to guide for how to use this library:

Django models

Let's consider we have the following django models:

class MusicTrack(models.Model):
    name = models.TextField()
    duration = models.IntegerField() # in seconds
    band = models.ForeignKey(Band, on_delete=models.DO_NOTHING)
    release_date = models.DateTimeField(null=True)

    class Meta:
        unique_together = ["name", "band"]

class Band(models.Model):
    name = models.TextField()
    custom_id = models.TextField(unique=True)

Faker

Factory boy has many ways to generate data, usually we prefer to use Faker, factory boy provides a wrapper for faker

Simple faker attribute

class MusicTrackFactory(factory.django.DjangoModelFactory):
    name = factory.Faker('name')

    class Meta:
        model = MusicTrack

Basically this is calling the name function in Faker library https://faker.readthedocs.io/en/latest/providers/faker.providers.person.html#faker.providers.person.Provider.name

Faker attribute with params

Let's say we want our music durations to be between 30 and 500 seconds we can use random_int like in https://faker.readthedocs.io/en/latest/providers/baseprovider.html#faker.providers.BaseProvider.random_int

❌ Common Mistake:

class MusicTrackFactory(factory.django.DjangoModelFactory):
    name = factory.Faker('name')
    duration = fake.random_int(min=30, max=500)

the issue with the implementation above is that it will generate a random number only once.

For example, when you would run a MusicTrackFactory.create() the duration would be some random number like 154, and the next time you run MusicTrackFactory.create() the duration would all be 154, instead of a new random number

✅ Correct Implementation:

class MusicTrackFactory(factory.django.DjangoModelFactory):
    name = factory.Faker('name')
    duration = factory.Faker('random_int',min=30, max=500)

Other providers

You can find more providers here. If you can't find the provider you need, you can also check here for community maintained providers.

If you still can't find the provider you need. You can always develop your own custom provider.

Custom providers

You can very easily create your own custom Faker providers to supplement your factories with better, more relevant data. For example, let's say you want better names for the MusicTrack model instead of generating a person's name.

import factory
from faker.providers import BaseProvider

class MusicTrackNameProvider(BaseProvider):
    __provider__ = "track_name"
    song_names_1 = [
        "The Awakening",
        "The Voyagers",
        "The Empyreal",
        "Of Carnage and",
        "Callisto",
        "The Scourge",
        "The Thirteen",
    ]
    song_names_2 = [
        "of the Stars",
        "Beneath the Mare Imbrium",
        "Lexicon",
        "a Gathering of the Wolves",
        "Rising",
        "of the Fourth Celestial Host",
        "Cryptical Prophecies of Mu",
    ]

    def track_name(self):
        return f"{self.random_element(self.track_names_1)} {self.random_element(self.track_names_2)}"

# This is the important bit that allows you to simply invoke the provider with the name "track_name".
factory.Faker.add_provider(MusicTrackNameProvider)

class MusicTrackFactory(factory.django.DjangoModelFactory):
    # Invoking the provider is as simple as calling the value defined in __provider__.
    name = factory.Faker("track_name")

As you can see, this is very simple to do and you can easily make your factories output data with higher quality. You can be as creative and versatile with the data that is generated as you need. For this example, it simply combines random values from two different lists.

For code-organization reasons, you can always implement your providers in a providers.py file and have your factories in the factories.py file.


Relations and Foreign Keys

https://factoryboy.readthedocs.io/en/stable/recipes.html#dependent-objects-foreignkey

You can use SubFactory to create other dependent models like:

class BandFactory(factory.django.DjangoModelFactory):
    name = factory.Faker('name')

class MusicTrackFactory(factory.django.DjangoModelFactory):
    name = factory.Faker('name')
    Band = factory.SubFactory(BandFactory)

Unique constraints

https://factoryboy.readthedocs.io/en/stable/orms.html#factory.django.DjangoOptions.django_get_or_create

for django we use django_get_or_create, for unique fields so we don't have exception when running tests

class BandFactory(factory.django.DjangoModelFactory):
    name = factory.Faker('name')
    custom_id = factory.Faker("lexify", text="???-###-????????-????")

    class Meta:
        model = Band
        django_get_or_create = ("custom_id",)

class MusicTrackFactory(factory.django.DjangoModelFactory):
    name = factory.Faker('name')
    band = factory.SubFactory(BandFactory)

    class Meta:
        model = MusicTrack
        django_get_or_create = ("name","band",)

Final Factories

class BandFactory(factory.django.DjangoModelFactory):
    name = factory.Faker('name')
    custom_id = factory.Faker("lexify", text="???-###-????????-????")

    class Meta:
        model = Band
        django_get_or_create = ("custom_id",)

class MusicTrackFactory(factory.django.DjangoModelFactory):
    name = factory.Faker('name')
    band = factory.SubFactory(BandFactory)
    release_date = factory.Faker('date_this_century',before_today=True)
    duration = factory.Faker('random_int',min=30, max=500)
    class Meta:
        model = MusicTrack
        django_get_or_create = ("name","band",)

If conditions within Factory (factory.Maybe Method)

Let's consider the following Factory example:

class BandFactory(factory.django.DjangoModelFactory):
    name = factory.Faker("name")
    custom_id = factory.Faker("lexify", text="???-###-????????-????")
    pseudonym = factory.Faker("boolean", chance_of_getting_true=0)
    band = factory.Maybe(
        "pseudonym",
        yes_declaration=factory.SubFactory("<project_name>.factories.BandFactory", pseudonym=False),
        no_declaration=None,
    )

In this example the attribute band will only be not null if the pseudonym attribute (which is a boolean field) has a value of true. To implement this we can use the factory.Maybe Method.