Unit testing your models in Django

As a good developer, you write unit tests, of course. You will probably even write your tests before implementing your logic in a test-driven approach!

However, when developing complex models which have interactions and foreign keys, writing tests can get messy and complicated.

Say you want to test a model which has many dependencies to other models via foreign keys. To create an instance of your model, you first need to create all the other model instances which your model uses and that can be pretty tiring.

So here comes mommy. Mommy takes care of everything like your mother does. She jumps in, creates your instance and all needed related instances in one swoop. Sounds good? It is great.

To install it, it’s as simple as: pip install model_mommy.

Example model

Let’s go ahead with a simple model example to further illustrate what mommy helps you with.

We want to model a manufacturer selling computers and will make a very simplistic schema (this is obviously just to illustrate) which goes like this:

  • A computer consists of a CPU, a hard disk and some RAM for the working memory
  • The user can configure their computer to consist of a combination of any of these 3 parts
  • Depending on the configuration, the computer has a price which is the sum of the 3 part prices

In Django this could roughly be this:

from django.db import models

class Cpu(models.Model):
    name = models.CharField()
    price = models.IntegerField()

class Harddisk(models.Model):
    name = models.CharField()
    price = models.IntegerField()

class Ram(models.Model):
    name = models.CharField()
    price = models.IntegerField()

class Computer(models.Model):
    name = models.CharField()
    cpu = models.ForeignKey(Cpu, models.PROTECT)
    harddisk = models.ForeignKey(Harddisk, models.PROTECT)
    ram = models.ForeignKey(Ram, models.PROTECT)
    price = models.IntegerField()

    def save(self, *args, **kwargs):
        if self.price is None:
            self.price = self.cpu.price + self.harddisk.price + self.ram.price
        super().save(*args, **kwargs)

Now, of course, we want to be sure that the price is calculated correctly, so we will have an accompanying unit test for it. In that test, we will need to create some CPU with a name and a price, some RAM with a name and a price and some hard disk with a name and a price to then finally create our Computer. Once the computer is created, we assert that its price really is the sum of the 3 parts. Quite a lot of setup boilerplate to create all these 3 parts when you really only want to test the Computer model…

So here comes mommy and makes the test as easy as it should be:

from django.test import TestCase
from .models import Computer
from model_mommy import mommy

class ComputerTests(TestCase):

    def test_computer_price_is_equal_to_the_sum_of_its_parts(self):
        computer = mommy.make(Computer)
        self.assertEqual(computer.price, computer.ram.price + computer.harddisk.price + computer.cpu.price)

Fantastic. No need to create either a RAM, a CPU or a hard disk and also no need to pass these things into the computer, just say you want to have a Computer and mommy will take care of generating all needed attributes and related objects as well.

Sometimes, however, you want to have a little more control over some attributes or relations, but no worries - mommy has you covered. Simply pass them in:

computer = mommy.make(Computer, name='Custom computer for Peter')
self.assertEqual(computer.name, 'Custom computer for Peter') # would be True

Moreover, if you want to control attributes of related items that is possible as well:

computer = mommy.make(Computer, ram__price=100, cpu__price=200, harddisk__price=50)
self.assertEqual(computer.price, 350)