anishfyi.com Projects All writings

November 17, 20259 min readTesting, pytest, Django

Testing a Django API with pytest

A pragmatic pytest setup for Django: fixtures, factories, database tests, and coverage that actually helps.

A backend without tests is a backend you are afraid to change. After keeping a Django codebase at full backend coverage with pytest, here is the setup that made tests fast enough to actually run, and useful enough to actually trust.

The setup

Install pytest-django and point it at the settings module in pytest.ini.

[pytest]
DJANGO_SETTINGS_MODULE = trampoline.settings
python_files = tests.py test_*.py *_tests.py

The database fixture

Tests do not touch the database unless they ask to. The django_db mark grants access, which keeps pure unit tests honest and fast.

import pytest


@pytest.mark.django_db
def test_order_defaults_to_received():
    order = Order.objects.create(reference="PO-1")
    assert order.status == "received"

Factories over fixtures of data

Hand-built test rows drift out of date. A factory builds a valid object with sensible defaults and lets each test override only the field it cares about.

import factory


class OrderFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Order

    reference = factory.Sequence(lambda n: f"PO-{n}")
    status = "received"

Test the API through the client

For endpoints, test through the framework test client. It exercises routing, authentication, serialization, and the view together, which is what a real caller hits. That is where the bugs that matter live.

Assert the query count

The django_assert_num_queries fixture turns a performance regression into a failing test. If a change adds a hidden query, the test catches it before production does.

Coverage that helps

Coverage measures which lines ran, not whether behavior was verified. Chasing a percentage for its own sake produces tests that execute code and assert nothing. Spend the effort on the unhappy paths: the rejected input, the permission denied, the conflicting update.

Keep the suite fast

Reuse the test database between runs, build objects in memory when a test does not need them persisted, and avoid the database entirely for logic that does not require it. A suite that runs in seconds gets run. A suite that takes ten minutes gets skipped.

Working on something in this territory? Book thirty minutes.