pytest Fixture Management for E2E Tests
TEST_DATA pytest+Django
Junior Edda · Updated 5 months, 1 week ago
Content
Skill: pytest Fixture Management for E2E Tests
Capability Domain: TEST_DATA
Technology Stack: pytest+Django
Overview
Patterns for managing test data and fixtures in E2E tests, including fixture files, data seeding, and test isolation strategies.
Reference Implementation
Pattern 1: JSON Fixture Files
// tests/fixtures/e2e_seed.json
[
{
"model": "auth.user",
"pk": 1,
"fields": {
"username": "admin",
"email": "admin@example.com",
"is_staff": true,
"is_superuser": true
}
},
{
"model": "methodology.playbook",
"pk": 1,
"fields": {
"name": "FeatureFactory",
"description": "Complete feature development methodology",
"category": "development",
"version": "3.8",
"status": "released",
"author": 1
}
},
{
"model": "methodology.workflow",
"pk": 1,
"fields": {
"name": "Build Feature",
"description": "Complete workflow for building features",
"playbook": 1,
"order": 1
}
}
]
Pattern 2: Loading Fixtures in Tests
# tests/e2e/conftest.py
import pytest
from django.core.management import call_command
@pytest.fixture(scope='session')
def django_db_setup(django_db_setup, django_db_blocker):
"""Load E2E test fixtures once per session."""
with django_db_blocker.unblock():
call_command('loaddata', 'tests/fixtures/e2e_seed.json')
@pytest.fixture
def e2e_data(db):
"""Ensure E2E data is available for each test."""
# Data already loaded by django_db_setup
# This fixture just ensures db is available
pass
Pattern 3: Factory-Based Fixtures
# tests/conftest.py
import pytest
from django.contrib.auth.models import User
from methodology.models import Playbook, Workflow, Activity
@pytest.fixture
def user_factory(db):
"""Factory for creating test users."""
def create_user(username='testuser', **kwargs):
defaults = {
'email': f'{username}@example.com',
'is_active': True
}
defaults.update(kwargs)
return User.objects.create_user(username=username, **defaults)
return create_user
@pytest.fixture
def playbook_factory(db, user_factory):
"""Factory for creating test playbooks."""
def create_playbook(name='Test Playbook', author=None, **kwargs):
if author is None:
author = user_factory()
defaults = {
'description': 'Test description',
'category': 'test',
'version': '0.1',
'status': 'draft'
}
defaults.update(kwargs)
return Playbook.objects.create(
name=name,
author=author,
**defaults
)
return create_playbook
@pytest.fixture
def workflow_factory(db, playbook_factory):
"""Factory for creating test workflows."""
def create_workflow(name='Test Workflow', playbook=None, **kwargs):
if playbook is None:
playbook = playbook_factory()
defaults = {
'description': 'Test workflow',
'order': 1
}
defaults.update(kwargs)
return Workflow.objects.create(
name=name,
playbook=playbook,
**defaults
)
return create_workflow
Pattern 4: Using Factories in Tests
# tests/integration/test_playbook_crud.py
import pytest
@pytest.mark.django_db
class TestPlaybookCRUD:
"""Test playbook CRUD operations."""
def test_create_playbook(self, user_factory, playbook_factory):
"""Test creating a playbook."""
user = user_factory(username='maria')
playbook = playbook_factory(
name='React Development',
author=user,
category='development'
)
assert playbook.id is not None
assert playbook.name == 'React Development'
assert playbook.author == user
def test_list_user_playbooks(self, user_factory, playbook_factory):
"""Test listing playbooks for a user."""
user1 = user_factory(username='maria')
user2 = user_factory(username='john')
playbook1 = playbook_factory(name='P1', author=user1)
playbook2 = playbook_factory(name='P2', author=user1)
playbook3 = playbook_factory(name='P3', author=user2)
user1_playbooks = Playbook.objects.filter(author=user1)
assert user1_playbooks.count() == 2
assert playbook1 in user1_playbooks
assert playbook2 in user1_playbooks
assert playbook3 not in user1_playbooks
Pattern 5: Test Isolation with Transaction Rollback
# tests/conftest.py
import pytest
@pytest.fixture(autouse=True)
def enable_db_access_for_all_tests(db):
"""Enable database access for all tests automatically."""
pass
# Each test runs in a transaction that's rolled back
# No need for manual cleanup
Pattern 6: Management Command for Seeding
# methodology/management/commands/seed_test_data.py
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
from methodology.models import Playbook, Workflow, Activity
class Command(BaseCommand):
help = 'Seed database with test data for E2E tests'
def handle(self, *args, **options):
"""Create test data."""
self.stdout.write('Seeding test data...')
# Create test user
user, created = User.objects.get_or_create(
username='admin',
defaults={
'email': 'admin@example.com',
'is_staff': True,
'is_superuser': True
}
)
if created:
user.set_password('admin123')
user.save()
self.stdout.write(f'Created user: {user.username}')
# Create test playbook
playbook, created = Playbook.objects.get_or_create(
name='FeatureFactory',
author=user,
defaults={
'description': 'Complete feature development methodology',
'category': 'development',
'version': '3.8',
'status': 'released'
}
)
if created:
self.stdout.write(f'Created playbook: {playbook.name}')
# Create test workflow
workflow, created = Workflow.objects.get_or_create(
name='Build Feature',
playbook=playbook,
defaults={
'description': 'Complete workflow for building features',
'order': 1
}
)
if created:
self.stdout.write(f'Created workflow: {workflow.name}')
self.stdout.write(self.style.SUCCESS('Test data seeded successfully'))
Common Pitfalls
❌ Don't: Create fixtures manually in each test
# Wrong - repetitive setup
def test_playbook_create():
user = User.objects.create_user(username='test')
playbook = Playbook.objects.create(name='Test', author=user)
# ... test logic ...
def test_playbook_update():
user = User.objects.create_user(username='test') # Duplicate
playbook = Playbook.objects.create(name='Test', author=user) # Duplicate
# ... test logic ...
✅ Do: Use factory fixtures
# Correct - reusable factories
def test_playbook_create(user_factory, playbook_factory):
user = user_factory()
playbook = playbook_factory(author=user)
# ... test logic ...
def test_playbook_update(user_factory, playbook_factory):
user = user_factory()
playbook = playbook_factory(author=user)
# ... test logic ...
❌ Don't: Share mutable data between tests
# Wrong - shared state
TEST_USER = User.objects.create_user(username='test')
def test_1():
TEST_USER.email = 'new@example.com' # Mutates shared state
TEST_USER.save()
def test_2():
assert TEST_USER.email == 'test@example.com' # Fails!
✅ Do: Create fresh data per test
# Correct - isolated data
@pytest.fixture
def test_user(db):
return User.objects.create_user(username='test')
def test_1(test_user):
test_user.email = 'new@example.com'
test_user.save()
# Changes rolled back after test
def test_2(test_user):
assert test_user.email == 'test@example.com' # Passes!
Quality Gates
- [ ] Fixture files created in
tests/fixtures/ - [ ] Factory fixtures defined in
conftest.py - [ ] Management command for seeding test data
- [ ] Test isolation via transaction rollback
- [ ] No shared mutable state between tests
- [ ] Fixtures scoped appropriately (function/class/session)
- [ ] All tests use fixtures (no manual data creation)
- [ ] Fixture data realistic and comprehensive
Fixture Organization
tests/
├── fixtures/
│ ├── e2e_seed.json # E2E test data
│ ├── journey_seed.json # Journey test data
│ └── playbooks_seed.json # Playbook-specific data
├── conftest.py # Shared fixtures
└── e2e/
└── conftest.py # E2E-specific fixtures
Fixture Scopes
- function (default): New instance per test function
- class: Shared across test class methods
- module: Shared across test module
- session: Created once per test session
@pytest.fixture(scope='function') # New per test
def test_user(db):
return User.objects.create_user(username='test')
@pytest.fixture(scope='session') # Once per session
def django_db_setup(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
call_command('loaddata', 'e2e_seed.json')
Best Practices
- Use factories for dynamic data: User factories, playbook factories
- Use JSON fixtures for static data: Seed data, reference data
- Scope fixtures appropriately: Function for isolation, session for performance
- Name fixtures descriptively:
authenticated_client,draft_playbook - Document fixture purpose: Docstrings explaining what fixture provides
- Keep fixtures DRY: Compose fixtures from other fixtures
- Isolate tests: Each test gets fresh data via transaction rollback
Details
- Capability Domain:
- TEST_DATA
- Technology Stack:
- pytest+Django
- Created:
- 5 months, 1 week ago
- Updated:
- 5 months, 1 week ago
Playbook
Activities Using This Skill 0
No activities are currently linked to this skill.