django-dev-test

Django Testing Patterns

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "django-dev-test" with this command: npx skills add sergio-bershadsky/ai/sergio-bershadsky-ai-django-dev-test

Django Testing Patterns

pytest-django testing with factory_boy for fixture management.

Core Principles

  • pytest only - Never use Django's TestCase

  • factory_boy - Use factories for all test data

  • Mirror structure - Tests mirror app structure

  • Isolation - Each test fully isolated

  • Fast fixtures - Prefer @pytest.fixture over setUp

Installation

pip install pytest pytest-django factory-boy pytest-cov

Configuration

pytest.ini or pyproject.toml :

pyproject.toml

[tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "config.settings" python_files = ["test_.py", "test.py"] python_classes = ["Test*"] python_functions = ["test*"] addopts = [ "--strict-markers", "-ra", "--tb=short", ] markers = [ "slow: marks tests as slow", "integration: marks tests as integration tests", ]

Test Structure

tests/ ├── conftest.py # Shared fixtures ├── factories/ │ ├── init.py │ ├── user.py # UserFactory │ ├── product.py # ProductFactory │ └── order.py # OrderFactory ├── unit/ │ ├── models/ │ │ ├── test_user.py │ │ └── test_product.py │ └── services/ │ └── test_user_service.py ├── integration/ │ └── api/ │ ├── test_users.py │ └── test_products.py └── e2e/ └── test_checkout.py

Conftest Setup

tests/conftest.py :

import pytest from django.test import Client from ninja.testing import TestClient

from apps.myapp.api import api

@pytest.fixture def client(): """Django test client.""" return Client()

@pytest.fixture def api_client(): """Django Ninja test client.""" return TestClient(api)

@pytest.fixture def authenticated_client(api_client, user): """API client with authentication.""" api_client.headers["Authorization"] = f"Bearer {user.get_token()}" return api_client

@pytest.fixture def user(user_factory): """Default test user.""" return user_factory()

@pytest.fixture def admin_user(user_factory): """Admin test user.""" return user_factory(is_staff=True, is_superuser=True)

Factory Pattern

Base factory in factories/init.py :

import factory from factory.django import DjangoModelFactory

class BaseFactory(DjangoModelFactory): """Base factory with common patterns."""

class Meta:
    abstract = True

@classmethod
def _create(cls, model_class, *args, **kwargs):
    """Override to handle soft-deleted models."""
    obj = super()._create(model_class, *args, **kwargs)
    return obj

User factory in factories/user.py :

import factory from factory import fuzzy from apps.users.models import User from . import BaseFactory

class UserFactory(BaseFactory): """Factory for User model."""

class Meta:
    model = User
    skip_postgeneration_save = True

email = factory.LazyAttribute(
    lambda obj: f"{obj.name.lower().replace(' ', '.')}@example.com"
)
name = factory.Faker("name")
is_active = True

@factory.post_generation
def password(obj, create, extracted, **kwargs):
    password = extracted or "testpass123"
    obj.set_password(password)
    if create:
        obj.save(update_fields=["password"])

@classmethod
def _create(cls, model_class, *args, **kwargs):
    """Create with explicit password handling."""
    password = kwargs.pop("password", None)
    user = super()._create(model_class, *args, **kwargs)
    if password:
        user.set_password(password)
        user.save(update_fields=["password"])
    return user

Order factory with relationships (factories/order.py ):

import factory from decimal import Decimal from apps.orders.models import Order, OrderItem from . import BaseFactory from .user import UserFactory from .product import ProductFactory

class OrderFactory(BaseFactory): """Factory for Order model."""

class Meta:
    model = Order

user = factory.SubFactory(UserFactory)
status = "pending"
total = factory.LazyAttribute(lambda obj: Decimal("0.00"))

@factory.post_generation
def items(obj, create, extracted, **kwargs):
    if not create:
        return

    if extracted:
        for item in extracted:
            OrderItemFactory(order=obj, **item)
    else:
        # Create 1-3 random items
        import random
        for _ in range(random.randint(1, 3)):
            OrderItemFactory(order=obj)

    # Update total
    obj.total = sum(item.subtotal for item in obj.items.all())
    obj.save(update_fields=["total"])

class OrderItemFactory(BaseFactory): """Factory for OrderItem model."""

class Meta:
    model = OrderItem

order = factory.SubFactory(OrderFactory)
product = factory.SubFactory(ProductFactory)
quantity = factory.fuzzy.FuzzyInteger(1, 5)
unit_price = factory.LazyAttribute(lambda obj: obj.product.price)

Register Fixtures

In conftest.py :

from tests.factories.user import UserFactory from tests.factories.product import ProductFactory from tests.factories.order import OrderFactory

@pytest.fixture def user_factory(): return UserFactory

@pytest.fixture def product_factory(): return ProductFactory

@pytest.fixture def order_factory(): return OrderFactory

Test Patterns

Model Tests

tests/unit/models/test_user.py

import pytest from apps.users.models import User

@pytest.mark.django_db class TestUserModel: def test_create_user(self, user_factory): user = user_factory(email="test@example.com", name="Test User")

    assert user.email == "test@example.com"
    assert user.name == "Test User"
    assert user.is_active is True
    assert user.id is not None

def test_soft_delete(self, user_factory):
    user = user_factory()
    user.delete()

    assert user.is_deleted is True
    assert User.objects.filter(id=user.id).exists()

def test_display_name(self, user_factory):
    user = user_factory(name="John Doe")
    assert user.display_name == "John Doe"

    user_no_name = user_factory(name="", email="jane@example.com")
    assert user_no_name.display_name == "jane"

Service Tests

tests/unit/services/test_user_service.py

import pytest from unittest.mock import patch, MagicMock from apps.users.services import UserService

@pytest.mark.django_db class TestUserService: def test_create_user(self, user_factory): user = UserService.create( email="new@example.com", name="New User", password="securepass123", )

    assert user.email == "new@example.com"
    assert user.check_password("securepass123")

def test_create_user_duplicate_email(self, user_factory):
    user_factory(email="existing@example.com")

    with pytest.raises(ValueError, match="already registered"):
        UserService.create(
            email="existing@example.com",
            name="Another User",
            password="password123",
        )

@patch("apps.users.services.email.send_welcome_email")
def test_create_user_sends_email(self, mock_send, user_factory):
    user = UserService.create(
        email="new@example.com",
        name="New User",
        password="password123",
    )

    mock_send.assert_called_once_with(user)

API Tests

tests/integration/api/test_users.py

import pytest

@pytest.mark.django_db class TestUsersAPI: def test_list_users_requires_auth(self, api_client): response = api_client.get("/users/") assert response.status_code == 401

def test_list_users(self, authenticated_client, user_factory):
    user_factory.create_batch(5)

    response = authenticated_client.get("/users/")

    assert response.status_code == 200
    data = response.json()
    assert len(data["items"]) >= 5

def test_create_user(self, authenticated_client):
    response = authenticated_client.post("/users/", json={
        "email": "new@example.com",
        "name": "New User",
        "password": "securepass123",
    })

    assert response.status_code == 201
    assert response.json()["email"] == "new@example.com"

def test_get_user(self, authenticated_client, user_factory):
    user = user_factory()

    response = authenticated_client.get(f"/users/{user.id}")

    assert response.status_code == 200
    assert response.json()["id"] == str(user.id)

def test_get_user_not_found(self, authenticated_client):
    import uuid
    fake_id = uuid.uuid4()

    response = authenticated_client.get(f"/users/{fake_id}")

    assert response.status_code == 404

Fixtures

See references/fixtures.md for advanced fixture patterns including:

  • Database transactions

  • File uploads

  • External service mocking

  • Time freezing

Additional Resources

Reference Files

  • references/fixtures.md
  • Advanced fixture patterns, mocking, parametrization

Related Skills

  • django-dev - Core Django patterns

  • django-dev-ninja - API patterns being tested

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

django-dev-ninja

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

django-dev-unfold

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

django-dev

No summary provided by upstream source.

Repository SourceNeeds Review
General

frappe-doctype

No summary provided by upstream source.

Repository SourceNeeds Review