Testing

drf-commons includes testing infrastructure to simplify writing tests for applications built on the library.

Test Factories

from drf_commons.common_tests.factories import (
    UserFactory,
    StaffUserFactory,
    SuperUserFactory,
)

Built with factory_boy.

UserFactory

Generates test users with sequential usernames:

user1 = UserFactory()                    # username="user1"
user2 = UserFactory()                    # username="user2"
custom = UserFactory(username="alice")   # username="alice"
with_email = UserFactory(email="alice@example.com")

By default:

  • Username: user{n} (sequentially numbered)

  • Email: {username}@example.com

  • Password: set via post_generation hook

StaffUserFactory

Like UserFactory but with is_staff=True:

staff = StaffUserFactory()

SuperUserFactory

Like UserFactory but with is_staff=True and is_superuser=True:

superuser = SuperUserFactory()

APIRequestFactoryWithUser

from drf_commons.common_tests.factories import APIRequestFactoryWithUser

A DRF APIRequestFactory wrapper that returns pre-authenticated DRF Request objects:

factory = APIRequestFactoryWithUser(user=UserFactory())

request = factory.get("/api/articles/")
request = factory.post("/api/articles/", data={"title": "Test"})
request = factory.patch("/api/articles/1/", data={"title": "Updated"})
request = factory.delete("/api/articles/1/")

All returned requests are DRF Request objects with request.user pre-set to the factory’s user.

Testing with Context User

Model-level tests that exercise UserActionMixin or CurrentUserField must set the context user manually:

from django.test import TestCase
from drf_commons.common_tests.factories import UserFactory
from drf_commons.current_user.utils import _set_current_user, _reset_current_user

class ArticleModelTest(TestCase):
    def test_created_by_auto_populated(self):
        user = UserFactory()
        token = _set_current_user(user)
        try:
            article = Article.objects.create(title="Test", content="Body")
            self.assertEqual(article.created_by, user)
            self.assertIsNotNone(article.created_at)
        finally:
            _reset_current_user(token)

    def test_updated_by_changes_on_save(self):
        initial_user = UserFactory()
        updating_user = UserFactory()

        token = _set_current_user(initial_user)
        try:
            article = Article.objects.create(title="Test", content="Body")
        finally:
            _reset_current_user(token)

        token = _set_current_user(updating_user)
        try:
            article.title = "Updated"
            article.save()
            article.refresh_from_db()
            self.assertEqual(article.updated_by, updating_user)
        finally:
            _reset_current_user(token)

Testing ViewSets

Use DRF’s APIClient for ViewSet integration tests:

from django.test import TestCase
from rest_framework.test import APIClient
from drf_commons.common_tests.factories import UserFactory

class ArticleViewSetTest(TestCase):
    def setUp(self):
        self.user = UserFactory()
        self.client = APIClient()
        self.client.force_authenticate(user=self.user)

    def test_list_returns_standardized_envelope(self):
        response = self.client.get("/api/articles/")
        self.assertEqual(response.status_code, 200)
        self.assertTrue(response.data["success"])
        self.assertIn("data", response.data)
        self.assertIn("timestamp", response.data)

    def test_create_returns_201(self):
        response = self.client.post(
            "/api/articles/",
            {"title": "Test Article", "content": "Body"},
            format="json",
        )
        self.assertEqual(response.status_code, 201)
        self.assertTrue(response.data["success"])

    def test_bulk_create(self):
        data = [
            {"title": f"Article {i}", "content": "Body"}
            for i in range(10)
        ]
        response = self.client.post(
            "/api/articles/bulk-create/",
            data,
            format="json",
        )
        self.assertEqual(response.status_code, 201)

Testing Soft Deletes

def test_soft_delete_does_not_remove_record(self):
    article = Article.objects.create(title="Test", content="Body")
    article.soft_delete()

    # Not returned by is_active queryset
    self.assertFalse(Article.objects.filter(is_active=True, pk=article.pk).exists())

    # Still in database
    self.assertTrue(Article.objects.filter(pk=article.pk).exists())

def test_restore_reactivates_record(self):
    article = Article.objects.create(title="Test", content="Body")
    article.soft_delete()
    article.restore()

    self.assertTrue(Article.objects.filter(is_active=True, pk=article.pk).exists())
    article.refresh_from_db()
    self.assertIsNone(article.deleted_at)

Testing Version Conflicts

from drf_commons.models.content import VersionConflictError

def test_version_conflict_raises(self):
    doc = Document.objects.create(body="Initial")

    # Simulate concurrent reads
    doc_a = Document.objects.get(pk=doc.pk)
    doc_b = Document.objects.get(pk=doc.pk)

    # First write succeeds
    doc_a.body = "Updated by A"
    doc_a.increment_version()
    doc_a.save()

    # Second write raises conflict
    doc_b.body = "Updated by B"
    doc_b.increment_version()
    with self.assertRaises(VersionConflictError):
        doc_b.save()

Integration Tests

drf-commons ships with integration tests in drf_commons/tests/:

Test File

Coverage

test_e2e.py

End-to-end API request/response cycles

test_bulk_update_modes.py

Bulk update vs. save mode equivalence

test_viewset_integration.py

ViewSet composition and action dispatch

test_middleware_integration.py

Middleware context propagation

test_service_integration.py

Import/export service integration

test_installation.py

Package import and configuration validation

Run all integration tests:

pytest drf_commons/tests/