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 with faker https://factoryboy.readthedocs.io/en/stable/reference.html#faker
- faker https://faker.readthedocs.io/en/latest/
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
Here is a list of other fakers classes https://faker.readthedocs.io/en/latest/providers.html
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.