Skip to main content

django-tdd

Django testing strategies with pytest-django, TDD methodology, factory_boy, mocking, coverage, and testing Django REST Framework APIs.

Source facts

Repository
Jamkris/everything-gemini-code
Last source activity
April 8, 2026 at 01:05
Detected SKILL.md language
English
Stars
93
Forks
23

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
django-tdd
description
Django testing strategies with pytest-django, TDD methodology, factory_boy, mocking, coverage, and testing Django REST Framework APIs.
# Django Testing with TDD Test-driven development for Django applications using pytest, factory_boy, and Django REST Framework. ## When to Use - Writing new Django applications - Implementing Django REST Framework APIs - Testing Django models, views, and serializers - Setting up testing infrastructure for Django projects ## TDD Workflow for Django ### Red-Green-Refactor Cycle ```python # Step 1: RED - Write failing test def test_user_creation(): user = User.objects.create_user(email='test@example.com', password='testpass123') assert user.email == 'test@example.com' assert user.check_password('testpass123') assert not user.is_staff # Step 2: GREEN - Make test pass # Create User model or factory # Step 3: REFACTOR - Improve while keeping tests green ``` ## Setup ### pytest Configuration ```ini # pytest.ini [pytest] DJANGO_SETTINGS_MODULE = config.settings.test testpaths = tests python_files = test_*.py python_classes = Test* python_functions = test_* addopts = --reuse-db --nomigrations --cov=apps --cov-report=html --cov-report=term-missing --strict-markers markers = slow: marks tests as slow integration: marks tests as integration tests ``` ### Test Settings ```python # config/settings/test.py from .base import * DEBUG = True DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', } } # Disable migrations for speed class DisableMigrations: def __contains__(self, item): return True def __getitem__(self, item): return None MIGRATION_MODULES = DisableMigrations() # Faster password hashing PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.MD5PasswordHasher', ] # Email backend EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # Celery always eager CELERY_TASK_ALWAYS_EAGER = True CELERY_TASK_EAGER_PROPAGATES = True ``` ### conftest.py ```python # tests/conftest.py import pytest from django.utils import timezone from django.contrib.auth import get_user_model User = get_user_model() @pytest.fixture(autouse=True) def timezone_settings(settings): """Ensure consistent timezone.""" settings.TIME_ZONE = 'UTC' @pytest.fixture def user(db): """Create a test user.""" return User.objects.create_user( email='test@example.com', password='testpass123', username='testuser' ) @pytest.fixture def admin_user(db): """Create an admin user.""" return User.objects.create_superuser( email='admin@example.com', password='adminpass123', username='admin' ) @pytest.fixture def authenticated_client(client, user): """Return authenticated client.""" client.force_login(user) return client @pytest.fixture def api_client(): """Return DRF API client.""" from rest_framework.test import APIClient return APIClient() @pytest.fixture def authenticated_api_client(api_client, user): """Return authenticated API client.""" api_client.force_authenticate(user=user) return api_client ``` ## Factory Boy ### Factory Setup ```python # tests/factories.py import factory from factory import fuzzy from datetime import datetime, timedelta from django.contrib.auth import get_user_model from apps.products.models import Product, Category User = get_user_model() class UserFactory(factory.django.DjangoModelFactory): """Factory for User model.""" class Meta: model = User email = factory.Sequence(lambda n: f"user{n}@example.com") username = factory.Sequence(lambda n: f"user{n}") password = factory.PostGenerationMethodCall('set_password', 'testpass123') first_name = factory.Faker('first_name') last_name = factory.Faker('last_name') is_active = True class CategoryFactory(factory.django.DjangoModelFactory): """Factory for Category model.""" class Meta: model = Category name = factory.Faker('word') slug = factory.LazyAttribute(lambda obj: obj.name.lower()) description = factory.Faker('text') class ProductFactory(factory.django.DjangoModelFactory): """Factory for Product model.""" class Meta: model = Product name = factory.Faker('sentence', nb_words=3) slug = factory.LazyAttribute(lambda obj: obj.name.lower().replace(' ', '-')) description = factory.Faker('text') price = fuzzy.FuzzyDecimal(10.00, 1000.00, 2) stock = fuzzy.FuzzyInteger(0, 100) is_active = True category = factory.SubFactory(CategoryFactory) created_by = factory.SubFactory(UserFactory) @factory.post_generation def tags(self, create, extracted, **kwargs): """Add tags to product.""" if not create: return if extracted: for tag in extracted: self.tags.add(tag) ``` ### Using Factories ```python # tests/test_models.py import pytest from tests.factories import ProductFactory, UserFactory def test_product_creation(): """Test product creation using factory.""" product = ProductFactory(price=100.00, stock=50) assert product.price == 100.00 assert product.stock == 50 assert product.is_active is True def test_product_with_tags(): """Test product with tags.""" tags = [TagFactory(name='electronics'), TagFactory(name='new')] product = ProductFactory(tags=tags) assert product.tags.count() == 2 def test_multiple_products(): """Test creating multiple products.""" products = ProductFactory.create_batch(10) assert len(products) == 10 ``` ## Model Testing ### Model Tests ```python # tests/test_models.py import pytest from django.core.exceptions import ValidationError from tests.factories import UserFactory, ProductFactory class TestUserModel: """Test User model.""" def test_create_user(self, db): """Test creating a regular user.""" user = UserFactory(email='test@example.com') assert user.email == 'test@example.com' assert user.check_password('testpass123') assert not user.is_staff assert not user.is_superuser def test_create_superuser(self, db): """Test creating a superuser.""" user = UserFactory( email='admin@example.com', is_staff=True, is_superuser=True ) assert user.is_staff assert user.is_superuser def test_user_str(self, db): """Test user string representation.""" user = UserFactory(email='test@example.com') assert str(user) == 'test@example.com' class TestProductModel: """Test Product model.""" def test_product_creation(self, db): """Test creating a product.""" product = ProductFactory() assert product.id is not None assert product.is_active is True assert product.created_at is not None def test_product_slug_generation(self, db): """Test automatic slug generation.""" product = ProductFactory(name='Test Product') assert product.slug == 'test-product' def test_product_price_validation(self, db): """Test price cannot be negative.""" product = ProductFactory(price=-10) with pytest.raises(ValidationError): product.full_clean() def test_product_manager_active(self, db): """Test active manager method.""" ProductFactory.create_batch(5, is_active=True) ProductFactory.create_batch(3, is_active=False) active_count = Product.objects.active().count() assert active_count == 5 def test_product_stock_management(self, db): """Test stock management.""" product = ProductFactory(stock=10) product.reduce_stock(5) product.refresh_from_db() assert product.stock == 5 with pytest.raises(ValueError): product.reduce_stock(10) # Not enough stock ``` ## View Testing ### Django View Testing ```python # tests/test_views.py import pytest from django.urls import reverse from tests.factories import ProductFactory, UserFactory class TestProductViews: """Test product views.""" def test_product_list(self, client, db): """Test product list view.""" ProductFactory.create_batch(10) response = client.get(reverse('products:list')) assert response.status_code == 200 assert len(response.context['products']) == 10 def test_product_detail(self, client, db): """Test product detail view.""" product = ProductFactory() response = client.get(reverse('products:detail', kwargs={'slug': product.slug})) assert response.status_code == 200 assert response.context['product'] == product def test_product_create_requires_login(self, client, db): """Test product creation requires authentication.""" response = client.get(reverse('products:create')) assert response.status_code == 302 assert response.url.startswith('/accounts/login/') def test_product_create_authenticated(self, authenticated_client, db): """Test product creation as authenticated user.""" response = authenticated_client.get(reverse('products:create')) assert response.status_code == 200 def test_product_create_post(self, authenticated_client, db, category): """Test creating a product via POST.""" data = { 'name': 'Test Product', 'description': 'A test product', 'price': '99.99', 'stock': 10, 'category': category.id, } response = authenticated_client.post(reverse('products:create'), data) assert response.status_code == 302 assert Product.objects.filter(name='Test Product').exists() ``` ## DRF API Testing ### Serializer Testing ```python # tests/test_serializers.py import pytest from rest_framework.exceptions import ValidationError from apps.products.serializers import ProductSerializer from tests.factories import ProductFactory class TestProductSerializer: """Test ProductSerializer.""" def test_serialize_product(self, db): """Test serializing a product.""" product = ProductFactory() serializer = ProductSerializer(product) data = serializer.data assert data['id'] == product.id assert data['name'] == product.name assert data['price'] == str(product.price) def test_deserialize_product(self, db): """Test deserializing product data.""" data = { 'name': 'Test Product', 'description': 'Test description', 'price': '99.99', 'stock': 10, 'category': 1, } serializer = ProductSerializer(data=data) assert serializer.is_valid() product = serializer.save() assert product.name == 'Test Product' assert float(product.price) == 99.99 def test_price_validation(self, db): """Test price validation.""" data = { 'name': 'Test Product', 'price': '-10.00', 'stock': 10, } serializer = ProductSerializer(data=data) assert not serializer.is_valid() assert 'price' in serializer.errors def test_stock_validation(self, db): """Test stock cannot be negative.""" data = { 'name': 'Test Product', 'price': '99.99', 'stock': -5, } serializer = ProductSerializer(data=data) assert not serializer.is_valid() assert 'stock' in serializer.errors ``` ### API ViewSet Testing ```python # tests/test_api.py import pytest from rest_framework.test import APIClient from rest_framework import status from django.urls import reverse from tests.factories import ProductFactory, UserFactory class TestProductAPI: """Test Product API endpoints.""" @pytest.fixture def api_client(self): """Return API client.""" return APIClient() def test_list_products(self, api_client, db): """Test listing products.""" ProductFactory.create_batch(10) url = reverse('api:product-list')
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub