Testing in Django (Part 2) – Model Mommy vs Django Testing Fixtures

Testing in Django (Part 2) – Model Mommy vs Django Testing Fixtures

In the last post, I introduced you to testing in Django and we looked at best practices as well as a few examples. This time, I’ll show you a bit more complicated example and introduce you to Model Mommy for creating sample data.

Why Should You Care?

In the last post, I said that, “factory_boy, model_mommy, and mock are all are used in place of fixtures or the ORM for populating needed data for testing. Both fixtures and the ORM can be slow and need to be updated whenever your model changes.”

To summarize, the Django Testing Fixtures are problematic because they:

  • must be updated each time your model/schema changes,
  • are really, really slow; and
  • sometimes hard-coded data can cause your tests to fail in the future.

So, by using Model Mommy you can create fixtures that load quicker and are much easier to maintain over time.

Django Testing Fixtures

Let’s start by looking at our example for testing the model in the last post:

Python
class WhateverTest(TestCase):

    def create_whatever(self, title="only a test", body="yes, this is only a test"):
        return Whatever.objects.create(
            title=title, body=body, created_at=timezone.now())

    def test_whatever_creation(self):
        w = self.create_whatever()
        self.assertTrue(isinstance(w, Whatever))
        self.assertEqual(w.__unicode__(), w.title)

Here we simply created a Whatever() object and asserted that the created title matched the expected title.

If you downloaded the Project from the repo, fire up the server and run the tests:

Shell
$ coverage run manage.py test whatever -v 2

You will see that the above tests pass:

Shell
test_whatever_creation (whatever.tests.WhateverTest) ... ok

Now, instead of having to create a new instance, with each attribute, each time (boring!), we can use Model Mommy to streamline the process.

Model Mommy

Install:

Shell
$ pip install model_mommy

Remember what our model looks like?

Python
class Whatever(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)

    def __unicode__(self):
        return self.title

Now we can re-write the above test in a much easier fashion:

Python
from model_mommy import mommy

class WhateverTestMommy(TestCase):

    def test_whatever_creation_mommy(self):
        what = mommy.make(Whatever)
        self.assertTrue(isinstance(what, Whatever))
        self.assertEqual(what.__unicode__(), what.title)

Run it. Did it pass?

How easy was that? No need to pass in arguments.

New Model

Let’s look at a bit more complicated example.

Setup

Create a new app:

Shell
$ python manage.py startapp whatevs

Add the app to the Installed_Apps in the settings.py file

Create the model:

Python
from django.db import models
from django.contrib.auth.models import User
from django.contrib import admin

class Forum(models.Model):
    title = models.CharField(max_length=100)

    def __unicode__(self):
        return self.title

class Post(models.Model):
    title = models.CharField(max_length=100)
    created = models.DateTimeField(auto_now_add=True)
    creator = models.ForeignKey(User, blank=True, null=True)
    forum = models.ForeignKey(Forum)
    body = models.TextField(max_length=10000)

    def __unicode__(self):
        return unicode(self.creator) + " - " + self.title

Sync the DB.

What does our coverage report look like?

Model Mommy test coverage

Test

Add the tests:

Python
from model_mommy import mommy
from django.test import TestCase
from whatevs.models import Forum, Thread

class WhateverTestMommy(TestCase):

    def test_forum_creation_mommy(self):
        new_forum = mommy.make('whatevs.Forum')
        new_thread = mommy.make('whatevs.Thread')
        self.assertTrue(isinstance(new_forum, Forum))
        self.assertTrue(isinstance(new_thread, Thread))
        self.assertEqual(new_forum.__unicode__(), new_forum.title)
        self.assertEqual(
            new_thread.__unicode__(), 
            (str(new_thread.forum) + " - " + str(new_thread.title)))

Re-run your tests (which should pass), then create the coverage report:

Shell
$ coverage run manage.py test whatevs -v 2
$ coverage html

Well?

Care to try running the above tests using JSON fixtures to see just how to set the tests up using the Django Testing Fixtures?

I’m not sure what we’ll have in store for the next tutorial, so let me know what you’d like to see. Grab the code here. Be sure to comment below if you have questions. Cheers!

🐍 Python Tricks 💌

Get a short & sweet Python Trick delivered to your inbox every couple of days. No spam ever. Unsubscribe any time. Curated by the Real Python team.

Python Tricks Dictionary Merge

About The Team

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

Master Real-World Python Skills With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

Master Real-World Python Skills
With Unlimited Access to Real Python

Locked learning resources

Join us and get access to thousands of tutorials, hands-on video courses, and a community of expert Pythonistas:

Level Up Your Python Skills »

What Do You Think?

Rate this article:

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!

Keep Learning

Related Tutorial Categories: advanced django testing web-dev