What to test in Django: Models
Tags: django, python, testing
Published: 2020-08-09 21:44:04

---
title: "What to test in Django: Models"
published: true
description: "Testing makes a lot of sense. But what should you test? Let's take a look at testing Django models."
tags: [django, python, testing]
canonical_url: "https://alcher.dev/2020/what-to-test-in-django-models/"
cover_image: "https://images.unsplash.com/photo-1581091014534-898da33e356e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1500&q=80"
---


## Introduction

Software testing is the discipline of determining correctness of a software. Automated tests, on the other hand, is the process of writing program that *tests a software automatically*. There is a whole plethora of discussions around the internet<sup><a href="#1">[1]</a></sup> about the upside of writing automated test suites, so I'm not going to bore you with my own opinions on the matter. But if you care about the quality of software you produce, and you already test your programs manually, I can confidently assure that having an automated test suite will increase your productivity and confidence with your codebase.

The [Django](https://djangoproject.com/) web framework provides a solid set of [testing facilities](https://docs.djangoproject.com/en/3.1/topics/testing/) out of the box adhering to the [batteries included](https://docs.python.org/3/tutorial/stdlib.html#tut-batteries-included) philosophy. With these already provided, <sup><a href="#2">[2]</a></sup>you can use a collection of tests — a *test suite* — to solve, or avoid, a number of problems:

- When you’re writing new code, you can use tests to validate your code works as expected.
- When you’re refactoring or modifying old code, you can use tests to ensure your changes haven’t affected your application’s behavior unexpectedly.

A Django project contains a bunch of moving parts which are prime candidates to write tests for. This article will focus on [Django Models](https://docs.djangoproject.com/en/3.1/topics/db/models/).

## Anatomy of a Django Model

Let's start by examining what a Django Model looks like. Using the [Sakila DVD Rental Database](https://dev.mysql.com/doc/sakila/en/) as a reference ([ER diagram](https://sp.postgresqltutorial.com/wp-content/uploads/2018/03/dvd-rental-sample-database-diagram.png)<sup><a href="#3">[3]</a></sup>), we might represent the `actor` table with this code:

```python
from django.db import models

# (1)
class Actor(models.Model):                             
    # (2)
    first_name = models.CharField(max_length=255)      
    last_name = models.CharField(max_length=255)
    last_update = models.DateTimeField(auto_now=True)

    # (3)
    film = models.ManyToManyToManyField(               
        "films.models.Film",
        related_name="actors",
        null=True,
        on_delete=models.SET_NULL,
    )

    # (4)
    def __str__(self):                                 
      return f"{self.first_name} {self.last_name}"
```

This code shows as a few things:

1. A Django model inherits from the `django.db.models.Model` class, allowing it to be mapped to a specific database table.
2. Instances of `django.db.models.Field`s can then be attached as properties to the model, allowing them to be mapped as table columns. Here we define a Many-to-Many relationship between an Actor and a Film. Note that the `null` and `on_delete` kwargs implies that an Actor doesn't necessarily need to have a Film attached to it.
3. [Relationships](https://docs.djangoproject.com/en/3.1/ref/models/fields/#module-django.db.models.fields.related) are an integral part of any RDBMS, and Django allows us to conveniently construct and access these relationships in an Object-Oriented manner.
4. Finally, models are just Python classes underneath the hood, so we can define custom (or *[magic](https://realpython.com/operator-function-overloading/#overloading-built-in-functions)*) methods for business/presentation/meta logic.

With this information in mind, we can now explore how and what we can test a Django Model.

## Testing Model Fields

Writing tests for a Model's fields are probably the easiest and requires the least effort. Basically, we would be asserting that a model does indeed have the expected fields, giving us guarantee that the corresponding table columns exist. Here's what it looks like:

```python
from datetime import datetime
from django.test import TestCase

from .models import Actor

# (1)
class ActorModelTest(TestCase):              
    # (2)
    @classmethod
    def setUpTestData(cls):                                     
        cls.actor = Actor.objects.create(
            first_name="John", 
            last_name="Doe"
        ) 

    # (3)
    def test_it_has_information_fields(self):                   
        self.assertIsInstance(self.actor.first_name, str)
        self.assertIsInstance(self.actor.last_name, str)

    # (4)
    def test_it_has_timestamps(self):                           
        self.assertIsInstance(self.actor.last_update, datetime)
```

Here's what this code does:

1. We subclass the Django `TestCase` class, which in turn inherits from the `unittest` module's [TestCase](https://docs.python.org/3/library/unittest.html#unittest.TestCase). This allows us to use assertion methods such as `assertTrue()` and `assertEquals()`, as well as some database access facilities such as `setUpTestData()`.
2. Using the aforementioned `setUpTestData()` class method, we create a single Actor instance to be used by the rest of the test methods. Read the [documentation](https://docs.djangoproject.com/en/3.0/topics/testing/tools/#django.test.TestCase.setUpTestData) on why we use this instead of the `setUp()` method from `unittest.TestCase`.
3. We then test that the first and last name fields do exist in the created Actor instance. We do this by running assertions on the specific properties that they are of a specific type, in this case `str`.
4. Similarly, we check if the `last_update` field exists as well, asserting that it is of the `datetime.datetime` type.

Running your test suite will then confirm that, indeed, the Actor model has the fields you expect!

## Testing Model Relationships

Testing for Model Relationships works similarly with Model Fields:

```python
# ...

from films.models import Film

class ActorModelTest(TestCase):
    #  ...
    
    def it_can_be_attached_to_multiple_films(self):
        # (1)
        films = [Film.objects.create() for _ in range(3)]      
        # (2)
        for film in films:
            film.actors.add(self.actor)                         
        
        # (3)
        self.assertEquals(len(films), self.actor.films.count()) 
        for film in films:
            # (4)
            self.assertIn(film, self.actor.films)               
```

The new test method should be self-explaining:

1. We create a bunch of Film objects to test with.
2. We then attach the Actor instance we created in the `setUpTestData()` method for each of the Film's related Actors.
3. Our Actor instance should have the same amount of films as we created.
4. And each of the films we created should be in the Actor's set of related films.

We can now be sure that our Models are connected properly.

## Intermezzo: What's up with these tests?

A lot of people would say that these methods are testing the framework code and that we didn't accomplish much by writing these tests. I say that they test *our integration with the framework* (i.e. testing that we use the correct Django's database access features) instead of testing *how the framework code works* (i.e. testing the internal implementation of an `IntegerField`). And those are perfectly fine in my book.

At the very least, these types or tests are very easy and fast to write. And momentum is very important in testing, doubly so if you follow [TDD](https://en.wikipedia.org/wiki/Test-driven_development). If still in doubt, tests like these are [easy to delete](https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to) down the line. 

Considere them a low-risk, medium-return investment on your portfolio of test suites.

## Testing Model Methods

Lastly, let's see how one would write a test for custom Model methods:

```python
# ...

class ActorModelTest(TestCase):
    #  ...

    # (1)
    def test_its_string_representation_is_its_first_and_last_name(self): 
        full_name = f"{self.first_name} {self.last_name}"
        # (2)
        self.assertEquals(str(self.actor), full_name)                    
```

1. A quick tip: tests are meant for validation, but they are also perfect as added documentation. Don't be afraid to write overly descriptive names for your test methods. One should be able to glance over a testcase and get an overhead view on how the specific module or class works.
2. Testing our `__str__()` method. Should be self-explanatory.

We can see that this is a pretty trivial scenario, but logic on custom methods can grow quickly if not kept in check. When more tables and fields are added down the line, we might have the following methods on the Actor class:

1. A good one is a `check_availability(timedelta)` method that checks if the Actor can be booked for a certain period of time.
2. Or a `compute_salary(movie)` for calculating how much an Actor should be paid for a specific Movie.
3. Maybe even a `retire()` method that sets some database fields for the Actor, signifying that the Actor no longer accepts work.

All of these methods require custom logic, and writing tests for these will give us confidence that our code adheres to what we expect it to do.

## Conclusion

In this article, we have touched on the following points:

- What software testing is, and what Django provides in its testing facilities.
- What a Django Model is made of.
- How to test a Model's fields.
- How to test a Model's relationships.
- How to test a Model's custom methods.

I hope you get something out of this article. May your tests be green and plenty. Stay safe!

## Footnotes

- <sup><span id="1">[1]</span></sup> a few links that you might find interesting:
    - [Why Would You Want To Automate a Test?](https://devqa.io/why-automate-a-test/)
    - [Obey The Testing Goat - Preface](https://www.obeythetestinggoat.com/book/preface.html)
    - [Jetbrains: Why Write Automated Tests](https://blog.jetbrains.com/idea/2015/08/why-write-automated-tests/)
    - [TDD is dead. Long live testing.](https://dhh.dk/2014/tdd-is-dead-long-live-testing.html)
- <sup><span id="2">[2]</span></sup> in verbatim, [https://docs.djangoproject.com/en/3.1/topics/testing/](https://docs.djangoproject.com/en/3.1/topics/testing/)
- <sup><span id="3">[3]</span></sup> from [https://www.postgresqltutorial.com/postgresql-sample-database/](https://www.postgresqltutorial.com/postgresql-sample-database/)