I’m sure we’ve all written tests for which you spend more time writing the setUp method than writing the actual tests. (This is probably an exaggeration, but it can feel that way when you spend longer than you’d like just creating all the objects you’ll need to write your tests). Objects depend on other objects and need to have specific attributes for your assertions. Factory Boy (modeled after Factory Girl in Rails) is a good way to get around this, and are often cleaner looking than fixtures. It will take some time to create your first set of factories, but once you have them, you can use them in all of your tests project wide, and won’t need to spend all that time on setup anymore. As always, here are the docs!
Factories at their most basic level look a lot like creating an object through the ORM. Use the Meta
class to tell your factory which model to use, and then give it whatever attributes you’d like:
import factory
from organization.models import Organization
class OrganizationFactory(factory.django.DjangoModelFactory):
class Meta:
model = Organization
name = 'organization name'
You can access the object within your tests like so:
from test_factories import OrganizationFactory
organization = OrganizationFactory()
If you need to override the automatically created attribute, you can pass a different value in when you use the factory, just like you would with ORM object creation:
organization = OrganizationFactory(name='something different')
Here, our organization instance will have a name of ‘something different instead of organization name.
If you’re going to be creating multiple instances of a given object and have uniqueness constraints on any attributes, you can add a sequential element to its value, like so:
import factory
from django.contrib.auth import get_user_model()
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
model = get_user_model()
username = factory.Sequence(lambda n: 'username_{}'.format(n))
password = 'password'
This will create a unique username each time this factory is called, with n incrementing automatically. Because passwords don’t require uniqueness, these can be passed in as a consistent string.
Because objects depend on other objects, Factory Boy allows you to use sub-factories within your factories. You can go as many levels deep with these as you need. This is a simplified example, but here, I’m really trying to create a Lesson
, but that requires a Course
, which requires an Organization
, and down the rabbit hole we go…
import factory
from courses.models import Course, Lesson
from organization.models import Organization
class OrganizationFactory(factory.django.DjangoModelFactory):
class Meta:
model = Organization
name = factory.Sequence(lambda n: u'Organization {}'.format(n))
class CourseFactory(factory.django.DjangoModelFactory):
class Meta:
model = Course
organization = factory.SubFactory(OrganizationFactory)
title = factory.Sequence(lambda n: 'Course Title {}'.format(n))
class LessonFactory(factory.django.DjangoModelFactory):
class Meta:
model = Lesson
course = factory.SubFactory(CourseFactory)
title = factory.Sequence(lambda n: 'Lesson Title {}'.format(n))
I would then have all of these objects just by creating a lesson with in my tests:
lesson = LessonFactory()
While this is convenient and saves you a lot of legwork, it can also obfuscate some of what is happening. This is something to be aware of if you are counting on using your tests to act as documentation for other developers who might not be as comfortable with your codebase or aware of all the relationships — if you use a factory to create a lesson, and this creates a course and an organization and more behind the scenes, others may have to dig a little deeper to discover this than if it were all in your setUp method in the same file as your tests.
Also, if you need to access the objects created by your subfactories, you will need to reference them through the main object you created, as they won’t be part of the factory’s return value (we have a lesson object, if we want our organization, we’ll have to get to it like this:lesson.course.organization).
Because objects won’t always have the same attribute values once you start using sequences, other attributes may need to be computed based on these values. For example, an Organization might have a slug, based off of its name. This is what LazyAttributes are for. In our case, we have a method that creates slugs within the models file for that object, so a factory might look something like this:
import factory
from organization.models import Organization, create_slug
class OrganizationFactory(factory.django.DjangoModelFactory):
class Meta:
model = Organization
name = factory.Sequence(lambda n: 'name_{}'.format(n))
slug = factory.LazyAttribute(lambda o: create_slug(o.name))
So in this case is the instance being constructed within the factory. If you don’t have a method for the thing you want, you can of course just use string formatting, with some attribute of o interpolated in. The docs have an example using user name attributes to create a unique email address.
Have fun building your factories!