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.comPassword: set via
post_generationhook
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 |
|---|---|
|
End-to-end API request/response cycles |
|
Bulk update vs. save mode equivalence |
|
ViewSet composition and action dispatch |
|
Middleware context propagation |
|
Import/export service integration |
|
Package import and configuration validation |
Run all integration tests:
pytest drf_commons/tests/