Using Fixture To Test Django

The Django’s ORM already has its own data loading mechanism for testing but you can use the Fixture module as an alternative. When using Fixture, you don’t have to deal with JSON or XML, you simply create DataSet objects in Python code and load them with an instance of DjangoFixture. Using Python code helps you share objects and common field definitions whereas in Django you might have many JSON files with the same field definitions in separate places. Using Python code also allows you to represent test data alongside your test code, take advantage of inheritance, and improve readability.

However, unlike Django, Fixture does not currently provide a way to auto generate DataSet classes from a real database. It is safe to mix Fixture style data loading and Django style data loading if desired.

This feature was contributed by Ben Ford.

Note

Fixture can only be used with Django version 1.0.2 and greater.

Example Django application

Here’s a simple blog application written in Django. The data model consists of Post objects that belong to Category objects.

from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User

class Category(models.Model):
    title       = models.CharField(_('title'), max_length=100)
    slug        = models.SlugField(_('slug'), unique=True)

    def __unicode__(self):
        return u'%s' % self.title

class Post(models.Model):
    title           = models.CharField(_('title'), max_length=200)
    author          = models.ForeignKey(User, blank=True, null=True)
    body            = models.TextField(_('body'))
    created         = models.DateTimeField(_('created'), auto_now_add=True)
    modified        = models.DateTimeField(_('modified'), auto_now=True)
    categories      = models.ManyToManyField(Category, blank=True)

    def __unicode__(self):
        return u'%s' % self.title

Note

A complete version of this blog app with fixtures and tests can be found in fixture/examples/django_example/blog/

Defining datasets

To load data into the test database, you first create some DataSet subclasses in Python code:

class UserData(DataSet):
    class Meta:
        django_model = 'auth.User'
    class ben:
        first_name = 'Ben'
        last_name = 'Ford'
        # ...

In this example, the nested class ben will be used to create a row in django’s auth.User model. Fixture knows to load into that model because of the django_model attribute of the inner Meta class. A couple other ways to link each DataSet to a specfic Django model are shown later on.

Defining relationships between models

More realistically you would need to load one or more foreign key dependencies as part of each DataSet. Here are the DataSets for testing the blog application, taken from fixture/examples/django_example/blog/datasets/blog_data.py

from fixture import DataSet, DjangoFixture
from blog.datasets.user_data import UserData

class BlogMeta:
    django_app_label = 'blog'
    
class CategoryData(DataSet):
    class Meta(BlogMeta):
        pass
    class python:
        title = 'python'
        slug = 'py'
    class testing:
        title = 'testing'
        slug = 'test'

class PostData(DataSet):
    class Meta(BlogMeta):
        pass
    class first_post:
        title           = "1st test post"
        body            = "this one's about python"
        author          = UserData.ben
        categories      = [CategoryData.python]
    class second_post(first_post):
        title           = "2nd test post"
        body            = "this one's also about python"
    class third_post(first_post):
        title           = "3rd test post"
        body            = "this one's about both"
        categories      = [CategoryData.python, CategoryData.testing]

Here first_post is a blog entry posted in the Python category and authored by Ben (notice the UserData class has been imported from the user_data module).

In this style, each DataSet class name starts with that of the Django model it should be loaded into and a shared class BlogMeta has the attribute django_app_label='blog' to tell the loader which app to find models in.

Note

In the current release of fixture you can only specify a relationship from the direction that the field is defined, not from the reverse (see DjangoMedium for more details).

Loading some data

To load these records programatically you’d use a DjangoFixture instance and a NamedDataStyle instance:

>>> from fixture import DjangoFixture
>>> from fixture.style import NamedDataStyle
>>> db_fixture = DjangoFixture(style=NamedDataStyle())

You would insert each defined row into its target database table via setup() :

>>> data = db_fixture.data(CategoryData, PostData)
>>> data.setup()
>>> Post.objects.filter(author__first_name='Ben').count()
3
>>> data.teardown()
>>> Post.objects.all()
[]

Foreign DataSet classes like UserData need not be mentioned in data() since they are loaded automatically when referenced.

Loading data in a test

Here is an example of how to create a unittest style class to test with data. This is taken directly from fixture.examples.django_example.blog.tests:

from fixture import DataSet, DjangoFixture
from fixture.style import NamedDataStyle
from fixture.django_testcase import FixtureTestCase
from datetime import datetime
from django.contrib.auth.models import User
from fixture.examples.django_example.blog.models import Post, Category
from fixture.examples.django_example.blog.datasets.blog_data import (
                                        UserData, PostData, CategoryData)

db_fixture = DjangoFixture(style=NamedDataStyle())

class TestBlogWithData(FixtureTestCase):
    fixture = db_fixture
    datasets = [PostData]
    
    def test_blog_posts(self):
        self.assertEquals(Post.objects.all().count(), 3,
                          "There are 3 blog posts")
        post = Post.objects.get(title=PostData.third_post.title)
        self.assertEquals(post.categories.count(), 2,
                          "The 3rd test post is in 2 categories")

    def test_posts_by_category(self):
        py = Category.objects.get(title=CategoryData.python.title)
        self.assertEquals(py.post_set.count(), 3,
                          "There are 3 posts in python category")

    def test_posts_by_author(self):
        ben = User.objects.get(username=UserData.ben.username)
        self.assertEquals(ben.post_set.all().count(), 3,
                          "Ben has published 3 posts")
        

Note

This test case uses Django’s fast data loading strategy introduced in 1.1 whereby data is removed by rolling back the transaction. If you need to test specific transactional behavior in your code then don’t use this test case.

See fixture.django_testcase.FixtureTestCase for details on how to configure the test case.

For further reading, check the API docs for fixture.django_testcase and fixture.loadable.django_loadable