Using Factory Boy in Django application tests
Factory Boy is a fixtures replacement for Django tests (and not only). In an easy and manageable way you can create and maintain data needed for tests. Fixtures can be hard to maintain. Direct ORM usage isn't the best thing too. Factory Boy is here to help...
You can find Factory Boy on github. There is also full documentation of the project.
Factory Boy allows you to create factories for models (not only for models). A factory is something like a wrapper that provides data required for object creation.
Factory Boy factories in action
Start with installing factory boy:When you got it we can start making factories for models. You can create them in factories.py file and import in the tests file.
In this article I'll use two basic models from my class based views article. Let us start with a simple category model:
class Category(models.Model):
title = models.CharField(max_length=150)
from demo.models import *
import factory
class CategoryFactory(factory.Factory):
FACTORY_FOR = Category
title = 'abc'
At start we have FACTORY_FOR - it points to model which this factory will cover. Next we have a list of model attributes and assigned values. For "title" we set "abc". Every created category will have such title.
We have a factory so now we can use it in a simple test - checking if category is displayed on the main page:from django.test import TestCase
import demo.factories
class DemoAppCategoryTest(TestCase):
def test_if_category_is_displayed(self):
category = demo.factories.CategoryFactory.create()
response = self.client.get('/')
self.assertTrue(category.title in response.content)
create method called on the factory will create the Category object needed for the test. self.client sends the request and in assert the test checks if category title is in the response content (returned page HTML code). You can run the tests for given app using python manage.py test APP NAME command.
Factories can do much more. One of handy features are sequences. Some fields will require unique values and setting fixed string won't work for them. Using a sequence will:
class CategoryFactory(factory.Factory):
FACTORY_FOR = Category
title = factory.Sequence(lambda n: 'Category ' + n)
First created category will get "Category 0" title while second will get "Category 1" and so on. You write tests and the factory does the dirty job of making all the objects with required data.
Here we have a more complex Entry model:
class Entry(models.Model):
category = models.ForeignKey(Category)
user = models.ForeignKey(User)
user_number = models.IntegerField()
system_number = models.IntegerField()
from django.contrib.auth.models import User
class UserFactory(factory.Factory):
FACTORY_FOR = User
username = factory.Sequence(lambda n: 'user' + n)
class EntryFactory(factory.Factory):
FACTORY_FOR = Entry
category = factory.SubFactory(CategoryFactory)
system_number = 1
user_number = 12345
user = factory.SubFactory(UserFactory)
In this example the EntryFactory has all the data required to create an object, but we can also pass values in the "create" method in tests.
Here is a simple test using that factory: def test_if_entry_is_displayed(self):
entry = demo.factories.EntryFactory.create()
response = self.client.get(reverse('entry-list', kwargs={'category_id': entry.category.id}))
phrase = '<b>user number</b>: %s' % entry.user_number
self.assertTrue(phrase in response.content)
class UserFactory(factory.Factory):
FACTORY_FOR = User
username = factory.Sequence(lambda n: 'user' + n)
password = 'abc'
@classmethod
def _prepare(cls, create, **kwargs):
password = kwargs.pop('password', None)
user = super(UserFactory, cls)._prepare(create, **kwargs)
if password:
user.set_password(password)
if create:
user.save()
return user
user = demo.factories.UserFactory.create()
self.client.login(username=user.username, password='abc')
Comment article