RkBlog

Hardware, programming and astronomy tutorials and reviews.

Using Factory Boy in Django application tests

Using factories to easily create and maintain data needed for 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:
pip install 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)
A factory for that model would look like so:
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()
This model has relations. Factory Boy can handle relations with subFactories:
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)
We can also create users that can be logged in. To do this we need a better UserFactory:
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
Each user will get "abc" password so we can login them:
user = demo.factories.UserFactory.create()
self.client.login(username=user.username, password='abc')
For testing Django topics you can check a href="https://docs.djangoproject.com/en/dev/topics/testing/">the documentation or check "Testing and Django" presentation on dstegelman-conf-notes.readthedocs.org.
RkBlog

13 June 2012;

Comment article