Using FactoryBoy with Django Tests

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!

Creating your first factory

import factoryfrom 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 OrganizationFactoryorganization = 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.

Sequences

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.

Subfactories

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).

Lazy Attributes

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))

o 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!

Senior Software Engineer | www.adriennedomingus.com

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store