Back to home

Django and Wagtail test factories

Table of Contents

Before adopting Django and Wagtail as my frameworks of choice for web development projects, I preferred Laravel. While there's a few things which Django handles differently to Laravel, one of the biggest things for my workflow was the lack of a built-in system for test model factories.

What is a factory?

A factory is a system that lets you easily mock data for your tests and creates as many records as you need for your test cases. When declaring a factory, you're giving yourself a tool to create database records for a given model with all the necessary fields pre-populated with randomised (and pre-declared) data.

What does Django use instead of factories?

Instead of programmatically creating dynamic data via factories, Django favours using fixtures.

"A fixture is a collection of files that contain the serialized contents of the database. Each fixture has a unique name, and the files that comprise the fixture can be distributed over multiple directories, in multiple applications."

Fixtures documentation

Creating fixtures requires you to manually create the data within a database and then serialise (save/export) it to a file. This file would typically be a CSV or TOML format and can then be loaded up at the beginning of your tests to provide data to test against.

While this approach is fine, it's not a workflow I'm personally comfortable with. I've found that it can be awkward and somewhat inflexible. When making database changes to a project with existing fixtures, updating the fixtures to match can be awkward and has cost me a fair amount of time when trying to adopt fixtures into my workflow.

factory-boy and wagtail-factories

To solve this problem I've used a combination of 2 excellent packages which are available on Pypi:

factory-boy

"As a fixtures replacement tool, it (factory-boy) aims to replace static, hard to maintain fixtures with easy-to-use factories for complex objects.

Instead of building an exhaustive test setup with every possible combination of corner cases, factory_boy allows you to use objects customized for the current test, while only declaring the test-specific fields"

factory-boy documentation

Defining a model factory is as easy as:

import factory
from . import models

class UserFactory(factory.Factory):
    class Meta:
        model = models.User

    first_name = factory.Faker('first_name')
    last_name = factory.Faker('last_name')
    admin = False

This factory can no dynamically create users for your database with randomised first and last names.

You can then utilise the factory and optionally override these fields like follows:

>>> UserFactory()
<User: Lucy Murray>
>>> UserFactory(first_name="Jack", last_name="Whitworth")
<User: Jack Whitworth>

wagtail-factories

wagtail-factories is built on top of factory-boy to provide base factory classes for Wagtail page models.

import factory
from wagtail_factories import PageFactory

from . import models

class BlogIndexPageFactory(PageFactory):
    title = factory.Faker("sentence", nb_words=4)

    class Meta:
        model = models.BlogIndexPage


class BlogPageFactory(PageFactory):
    parent = factory.SubFactory(BlogIndexPageFactory)
    title = factory.Faker("sentence", nb_words=4)

    class Meta:
        model = models.BlogPage

This example shows how we can use the wagtail_factories.PageFactory class to create page factories for our own models. These factories are using factory.Faker to dynamically title the pages, and then factory.SubFactory to automatically assign page parents where required.

Writing tests with factories

Finally, putting this all together, here's a short example of some unit tests written for a blog index page's get_context method with some extra commenting to help explain what's happening:

class BlogIndexPageTestCase(TestCase):
    def test_get_context_returns_children_of_this_index(self):
        """Test get_context returns only blogs that are children of this index."""

        # Create a blog index with 3 blog posts
        expected_length = 3
        blog_index = BlogIndexPageFactory()
        blog_pages = BlogPageFactory.create_batch(expected_length, parent=blog_index)

        # Create another blog index with 2 blog posts
        other_blog_index = BlogIndexPageFactory()
        other_blog_pages = BlogPageFactory.create_batch(2, parent=other_blog_index)

        # Fetch the results of the `get_context` method
        request = RequestFactory().get("/")
        context = blog_index.get_context(request)
        blog_entries = context["blog_entries"]

        # Assert that we have the correct number of results
        self.assertEqual(len(blog_entries), expected_length)
        for blog in blog_entries:
            # Assert the posts are those we created for `blog_index`
            self.assertIn(blog, blog_pages)
            self.assertNotIn(blog, other_blog_pages)

Related blogs

  • django-testing-patterns
    Useful Django unit test patterns

    This is a collection of some common unit test patterns I find myself re-implementing across various projects. To save myself (and maybe some others) time in the future, I've decided to collect them all in one place.

    20 March 2026
  • writing-wagtail-streamfield-content-migrations
    Writing Wagtail StreamField content migrations

    When you drastically change one of your StreamFields and generate a migration for your new structure, the migration won't actually update what's already in your database. It'll describe the new, intended structure behind the fields, but the JSON data itself will remain exactly as it is.

    18 February 2026
  • directly-filtering-wagtail-pages
    Directly filtering Wagtail parent/child pages

    In Wagtail, pages don't have a typical, direct relationship like you'd expect in Django. Instead of using foreign keys to link them, each page has a `path` and a `depth` attribute...

    24 November 2025