Django cookbook · Beginner

Test views and APIs with pytest-django

Tests are slow, depend on each other, and don't catch query regressions.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Use pytest-django fixtures for the database and clients, small factory functions for data, and assertions on status codes, response bodies and query counts.

Solution

# conftest.py
import pytest
from rest_framework.test import APIClient


@pytest.fixture
def user(django_user_model):
    return django_user_model.objects.create_user(email="[email protected]", password="pw-123456789")


@pytest.fixture
def api(user):
    client = APIClient()
    client.force_authenticate(user)
    return client


# test_notes.py
import pytest

from notes.models import Note


@pytest.mark.django_db
def test_lists_only_own_notes(api, user, django_user_model, django_assert_max_num_queries):
    other = django_user_model.objects.create_user(email="[email protected]", password="pw-123456789")
    Note.objects.create(owner=user, title="mine")
    Note.objects.create(owner=other, title="theirs")

    with django_assert_max_num_queries(3):
        response = api.get("/api/notes/")

    assert response.status_code == 200
    assert [n["title"] for n in response.json()["results"]] == ["mine"]


@pytest.mark.django_db
def test_rejects_anonymous(client):
    assert client.get("/api/notes/").status_code in (401, 403)

Pitfalls

  • Sharing fixtures with module scope that mutate the database between tests.
  • Fixtures with huge object graphs; build only what each test needs.
  • Not testing permissions and other users' data, the most common API bug.

Variations

  • factory_boy or model_bakery generate model instances with sensible defaults.
  • Run with --reuse-db locally and -n auto (pytest-xdist) in CI.

Further reading: pytest-django

Reference: https://pytest-django.readthedocs.io/en/latest/

More Django recipes

Was this page helpful?