Suppose you are using Factory Boy to create a factory for the following example Django models.
class Group(models.Model): name = models.CharField(max_length=100, unique=True) class User(models.Model): first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) group = models.ForeignKey(Group)
Notice our Group
class has a unique constraint on the name
column.
The problem
Now let's say our factories for these models looked like so:
class GroupFactory(factory.DjangoModelFactory): name = factory.Sequence(lambda n: 'Group {0}'.format(n)) class Meta: models = models.Group class AdminGroupFactory(GroupFactory): name = 'ADMIN' class AdminUserFactory(factory.DjangoModelFactory): first_name = factory.Sequence(lambda n: 'Admin First {0}'.format(n)) last_name = factory.Sequence(lambda n: 'Admin Last {0}'.format(n)) group = factory.SubFactory(AdminGroupFactory) class Meta: models = models.User
In our AdminGroupFactory
, we've hardcoded the name
to 'ADMIN'
.
Perhaps our application looks for this special group when it comes to user
permissions, and some tests expect this Group to exist.
However, if we ever instantiate AdminGroupFactory
more than once at any point
in a test (whether within the testcase itself or through SubFactory chaining),
we'll recieve an error resembling the following:
duplicate key value violates unique constraint "app_group_name_key" DETAIL: Key (name)=('ADMIN') already exists.
In our example, creating two instances of AdminUserFactory
will be enough to
trigger the error. This is because each AdminUserFactory
instantiation causes
a AdminGroupFactory
instantiation, resulting in an attempt to create multiple
Group entries with the name 'ADMIN'
. This violates the unique constraint.
Solution
Within the actual Django application, we have a way of quickly dealing with the issue of creating/retrieving a Model instance in a single step: get_or_create.
Factory boy provides a similar solution for the problem demonstrated above.
class GroupFactory(factory.DjangoModelFactory): name = factory.Sequence(lambda n: 'Group {0}'.format(n)) class Meta: models = models.Group django_get_or_create = ('name',)
The django_get_or_create
Meta option takes a tuple of field names you would
like passed to Django's get_or_create
. You should pass the This makes it safe to instantiate
factories that may contain SubFactories with unique constraints.
You don't have to worry about passing all fields to the django_get_or_create
option - factory boy ensures the fields not passed to the django_get_or_create
will still have their values added as part of the creation process. (Proof)