Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 55

Testing Django Models

Recommended resources

Hello! Welcome back.

In our last lesson, we covered the "why" of automated testing, establishing its crucial role in building robust, maintainable applications. You learned that tests act as a safety net, prevent regressions, and are a hallmark of professional software development.

Today, we transition from theory to practice. This lesson is all about the "how." We will write our first unit tests for Django models, focusing specifically on verifying the custom methods and properties you add to them. This is the first hands-on step towards building a solid test suite for your Task Manager portfolio project.

1. The Anatomy of a Model Test

Before we write code, let's understand where it goes and what it looks like. When we test a model, we are primarily interested in verifying the custom business logic we've added to it.

Django Models Components
A Django model is more than just database fields. It also includes methods and properties that define its behavior. It's this custom behavior—the "Methods" part—that we'll focus on testing today.

Django has a built-in test framework based on Python's standard unittest library. It automatically discovers and runs tests that follow a specific structure.

Let's learn about this structure from the excellent Mozilla Developer Network (MDN) guide.

Django Tutorial Part 10: Testing a Django web application

This resource explains the standard structure for organizing tests in a Django application and introduces the key components you'll be using.

Please read the section titled 'Test structure overview'. Focus on understanding the recommended file structure (the tests directory) and the roles of TestCase, setUpTestData(), and setUp().

To summarize the key structural points:

  • Location: Tests for an app are typically placed in a tests/ directory within that app (e.g., task_manager_app/tests/). The test files themselves should be named starting with test_, such as test_models.py or test_views.py.
  • Test Class: Your tests are organized into classes that inherit from django.test.TestCase. This class provides a suite of tools and assertion methods and, importantly, runs each test within its own database transaction, ensuring tests are isolated from each other.
  • Test Setup:
    • setUpTestData(): A class method that runs once per test class. It's perfect for creating objects that will be used by all tests in the class but will not be modified.
    • setUp(): A method that runs before every single test method. It's used for creating objects that might be changed during a test, ensuring each test starts with a fresh, clean state.
  • Test Methods: Individual tests are methods within the class whose names start with test_.

2. Writing and Running Your First Model Test

Now, let's see these concepts in action. We'll walk through a complete example of testing a model. This example tests not only custom methods like __str__ and get_absolute_url but also properties of the model's fields, like their max_length.

The following reading provides a clear, practical example. As you read, try to spot the "Arrange, Act, Assert" pattern in each test method:

  1. Arrange: Set up the necessary data and objects (often done in setUpTestData).
  2. Act: Perform the action you want to test (e.g., call a method).
  3. Assert: Check that the outcome of the action is what you expected.

Django Tutorial Part 10: Testing a Django web application

This section from the MDN guide provides a concrete example of a test class for an Author model. It's a perfect template for how you will test your own models.

Read the 'Models' section under the 'LocalLibrary tests' heading. Study the AuthorModelTest class carefully. Pay attention to how setUpTestData is used and the different things being tested in each test_ method.

Let's break down one of the tests from the article, test_object_name_is_last_name_comma_first_name, which verifies the __str__ method:

# From the MDN article
def test_object_name_is_last_name_comma_first_name(self):
    # ARRANGE: The author object was already created in setUpTestData.
    # We just need to retrieve it.
    author = Author.objects.get(id=1)

    # ACT: We prepare the expected string and get the actual string
    # by calling str() on the object, which invokes __str__().
    expected_object_name = f'{author.last_name}, {author.first_name}'
    actual_object_name = str(author)

    # ASSERT: We check if the actual result matches the expected result.
    self.assertEqual(actual_object_name, expected_object_name)

This is the fundamental pattern you will use for all your tests.

To run your tests, you use a manage.py command:

# Run all tests in the project
python manage.py test

# Run all tests for a specific app
python manage.py test your_app_name

3. Testing Custom Properties and Logic

While the MDN article is great for custom methods, your models might also have custom properties, often defined using the @property decorator. These properties often compute a value based on other fields or related models.

Let's watch a video that demonstrates exactly this. The example involves a Project model with a budget_left property that calculates the remaining budget after some expenses have been logged. This is a very realistic use case.

Django Testing Tutorial - Testing Models #4

This video from 'The Dumbfounds' shows how to test a custom model property that depends on related objects. It's a great example of the 'Arrange, Act, Assert' pattern for slightly more complex logic.

First, watch from the beginning to 01:00 to see how the test class is set up with a Project object in the setUp method. Then, watch from 02:02 to 03:38, which focuses on testing the budget_left property. Notice how they first 'arrange' the data by creating Expense objects before 'asserting' the final value.

In the video, the test for budget_left follows our pattern perfectly:

  1. Arrange: A Project is created in setUp. Inside the test method, two Expense objects are created and linked to that project.
  2. Act: The self.project1.budget_left property is accessed.
  3. Assert: self.assertEqual is used to verify that the value of the property is 7000 (the initial 10,000 budget minus 3,000 in expenses).

This shows how you can test logic that spans multiple related models.

Test your understanding!

For your Task Manager project, imagine your Task model has a due_date (DateField) and a completed_at (DateTimeField, can be null). You want to add a custom method is_overdue() that returns True if the task is not complete and its due_date has passed.

How would you write a test method test_is_overdue_is_false_when_task_is_complete? Describe the "Arrange, Act, and Assert" steps.

Show answer

Here is how you would structure the test:

  1. Arrange: In your setUp or test method, create a Task instance. Set its due_date to a date in the past (e.g., yesterday). Crucially, also set its completed_at to a specific datetime (e.g., now).
  2. Act: Call the is_overdue() method on your task instance.
  3. Assert: Use self.assertFalse(task.is_overdue()) to verify that the method returns False, because even though the due date has passed, the task is marked as complete.

(You would then write another test to check the case where completed_at is None and the due date is in the past, where you'd expect self.assertTrue().)


Conclusion

You've now covered the fundamentals of writing unit tests for Django models. This is a critical skill for ensuring the data layer of your application is reliable and behaves exactly as you intend.

Key Takeaways:

  • Model tests are organized in classes inheriting from django.test.TestCase and live in files like app/tests/test_models.py.
  • Use setUpTestData() for creating shared, unmodified test data, and setUp() for data that needs to be fresh for each test.
  • Every test follows the Arrange, Act, Assert pattern to ensure it is clear, focused, and verifiable.
  • You use assertion methods like self.assertEqual() and self.assertTrue() to check if your code produced the expected outcome.
  • Testing custom methods (__str__, get_absolute_url()) and properties (@property) is essential for verifying your application's unique business logic.

Preview of the Next Lesson:

With our model logic now under test, we can move up the application stack. In the next lesson, we will write tests for our views. You'll learn how to use Django's test client to simulate browser requests, check for correct HTTP status codes, verify that the right template is being used, and ensure the context data sent to the template is correct.