Populate Your Test Database With Mixins
·Maintaining fixtures got you down? Use a mixin!
While Django has support for loading
fixtures
in its unit testing tools, I have found that maintaining fixtures over time is
nothing less than a giant pain in the butt. As your model definitions change
over time, remembering to re-run manage.py dumpdata myapp >fixtures.json
any
time your fixtures are affected is something I guarantee you will forget to do.
Not to mention the fact that your database may or may not contain records
suitable for testing at any given time. Then all of your unit tests have blown
up, it takes you a little bit of time to figure out why, and before you know it
you are cursing and out of your flow.
For a time I tried using Alex Gaynor’s django-fixture-generator but that only solved the problem of creating the records you want to test, not the problem of fixtures sitting on disk that are out of sync with your model schema.
After being introduced to Django class-based views’ mixin-heavy style, I cooked
up a technique for unit test TestCase
classes to create the records they need
at runtime, thus bypassing the need for fixtures. In an apps
tests/__init__.py
I create a PopulateDbMixin
:
class PopulateDbMixin(object):
def populate_db(self):
"""Assume our app has Recipe and Ingredient models"""
self.models = {}
self.models['recipes'] = {}
self.models['recipes']['bread'] = \
Recipe.objects.create(name='bread')
self.models['ingredients'] = {}
self.models['ingredients']['flour'] = \
Ingredient.objects.create(name='flour')
self.models['ingredients']['yeast'] = \
Ingredient.objects.create(name='yeast')
We can now include this mixin in our TestCase
classes and run its
populate_db
method in setUp
and have freshly baked bread in each of our
test methods:
class DeliciousTestCase(PopulateDbMixin, TestCase):
def setUp(self):
self.populate_db()
def test_can_bake_bread(self):
bread_recipe = self.models['recipes']['bread']
yeast = self.models['ingredients']['yeast']
bread_recipe.ingredients.add(yeast)
self.assert_(not bread_recipe.goes_horribly_wrong())
The Django TestCase
will take care of clearing out the database in between
each run; we’re just running populate_db
instead of the usual fixture-loading
pattern.
This particular technique may be old as time and in use by many teams, but I haven’t seen it written down anywhere so I thought I would share.