Should I test that?

Should I test models?

Verdict

Yes

Test the rules you write into a model, such as validations, calculated values and query scopes, by saving and reading records in a test database; do not test the field declarations that the ORM maps for you.

Why

Test the rules you put into a model, and leave the field mapping to the ORM. The typical case is a Django, Rails or Laravel model with a few fields, two or three validations and a method, such as a booking that counts its nights. Blast radius is users and Change frequency is regularly, because a wrong rule shows customers wrong data and models gain rules with most features. Detectability is eventually, because a broken validation saves invalid records without an error. Reversibility is with-effort, because bad records stay until a repair script fixes them, and Test cost is moderate, because a model test needs a test database, and rule R11 gives Test.

When the decision changes
WhenDecisionWhy
The model only declares fields that map to table columns, with no validations or methodsDo not test the model; test the code that reads and writes its fieldsDetectability moves to immediately, Reversibility to trivial and Test cost to trivial, because the ORM or the migration check rejects a field that does not match the table
A model method calculates an amount that customers pay, such as an invoice totalTest mandatory: the main path, every known failure and the boundary amountsBlast radius rises to money and Reversibility to costly
A default scope limits every query to the records of the current tenantTest mandatory, including a query that must not return the records of another tenantBlast radius rises to safety-or-legal and Detectability to never, because a broken scope returns other customers' data without an error
A NOT NULL, CHECK or unique constraint in the database also enforces the model ruleTest minimally: one test that the database rejects an invalid recordDetectability moves to same-day and Reversibility to trivial, because the database refuses the bad row and stores nothing
Saving one valid record in a test needs a dozen related records and seeded reference dataTest it differently: a nightly query counts records that break the model rule and alerts above zeroTest cost rises to heavy while Detectability stays eventually and Reversibility with-effort

What breaks if you don't test

A validation deleted during a refactor, or skipped by one save path, lets in records the rest of the code assumes cannot exist: a booking that ends before it starts, an order without a customer. Nothing crashes on save. The failure shows days later in a report or a calendar, and every record saved since the change needs a repair.

What you lose if you over-test

The common over-test asserts what the ORM guarantees: that name is a CharField with max_length=100, or that user.orders returns orders. These tests repeat the model definition, so every new field costs two edits. A mocked ORM wastes time too: the test checks your mock, and a scope with a wrong filter passes it.

How to test

Write integration tests against the database engine that production runs, inside a transaction the test framework rolls back, as Django's TestCase and Rails model tests do. For each validation, save one valid and one invalid record through the call that production code uses. For each calculated value, test the main input and the boundaries; for each query scope, create one record that matches and one that must not. See the Django testing overview and the Rails guide on testing models.

When the answer changes

  • A model method calculates money, or a scope decides whose records a user may see.
  • The model holds no rules, which makes it a data transfer object with a table behind it.
  • Building one valid record in a test takes more setup than the rule you check.

Real incident + Code example

The bookings that skipped validation

On a Django booking service I worked on, the Booking model checked in clean() that the end date came after the start date, and its test called full_clean() and passed. The partner import job used Booking.objects.create(), and Django does not call full_clean() from save(), as its validation documentation states. For three weeks the job saved 37 bookings with reversed dates, the calendar showed those rooms as free, and we found out when two guests arrived for the same room. We moved the rule into a CheckConstraint, repaired the records by hand, and made the test save through the call the import job makes:

from datetime import date

from django.db import IntegrityError, transaction
from django.test import TestCase

from bookings.models import Booking, Room


class BookingModelTest(TestCase):
    def test_create_rejects_end_before_start(self):
        room = Room.objects.create(number="101")
        with self.assertRaises(IntegrityError), transaction.atomic():
            Booking.objects.create(
                room=room, start=date(2026, 9, 20), end=date(2026, 9, 18)
            )

    def test_nights_counts_each_night_once(self):
        booking = Booking(start=date(2026, 9, 20), end=date(2026, 9, 23))
        self.assertEqual(booking.nights(), 3)

The constraint holds for every path that writes a booking, including bulk_create().

FAQ

Should model classes be tested?

Yes, test a model class when it holds rules such as validations, calculated values or query scopes. A model class that only declares fields needs no test of its own, because tests of the code that reads the fields fail when a field is wrong.

Should Django models be tested?

Yes, test the validations, methods and custom managers you add to a Django model, using django.test.TestCase. Do not test field options such as max_length, because Django's own suite covers them and makemigrations --check in CI catches a model that no longer matches its migrations.

Should model tests use a real database?

Yes, model tests should use a real database of the engine production runs. A mocked ORM cannot check a query filter or a unique constraint, and SQLite ignores VARCHAR lengths, so a string that PostgreSQL rejects passes on SQLite.

Should I test model validations?

Yes, test each validation with one valid and one invalid record, saved through the call production code uses. When the rule fits a database constraint, add the constraint too, because a model validation runs only on the save paths that call it.