Pytesting
Testing
Setup
Installation
pip install pytest pytest-django
Configuration (pytest.ini)
[pytest]
DJANGO_SETTINGS_MODULE = your_project.settings
python_files = tests.py test_*.py *_tests.py
filterwarnings =
ignore::DeprecationWarning
ignore::django.utils.deprecation.RemovedInDjango50Warning
or #### pyprojecrt.toml file
[tool.pytest.ini_options]
django_settings_module = "myproject.settings" # Replace 'myproject' with your actual project name
python_files = ["tests.py", "test_*.py", "*_tests.py"]
addopts = "--reuse-db --tb=short -p no:warnings"
Basic Concepts
Test File Structure
# tests/
# └── test_views.py
# └── test_models.py
# └── test_forms.py
# └── test_api.py
# └── conftest.py # shared fixtures
Simple Test Example
def test_homepage_status(client):
= client.get('/')
response assert response.status_code == 200
Fixtures
Basic Fixtures
import pytest
from django.contrib.auth.models import User
@pytest.fixture
def user_data():
return {
'username': 'testuser',
'password': 'testpass123',
'email': 'test@example.com'
}
@pytest.fixture
def user(db, user_data):
return User.objects.create_user(**user_data)
@pytest.fixture
def admin_user(db):
return User.objects.create_superuser(
='admin',
username='admin123',
password='admin@example.com'
email )
Factory Boy Integration
import factory
from myapp.models import Profile
class UserFactory(factory.django.DjangoModelFactory):
class Meta:
= User
model
= factory.Sequence(lambda n: f'user{n}')
username = factory.LazyAttribute(lambda o: f'{o.username}@example.com')
email
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
= Profile
model
= factory.SubFactory(UserFactory)
user = factory.Faker('text')
bio
@pytest.fixture
def user_with_profile():
return ProfileFactory()
Database Testing
Basic Model Testing
@pytest.mark.django_db
def test_create_user(user_data):
= User.objects.create_user(**user_data)
user assert User.objects.count() == 1
assert user.username == user_data['username']
@pytest.mark.django_db
def test_profile_creation():
= UserFactory()
user = ProfileFactory(user=user)
profile assert profile.user == user
Query Testing
@pytest.mark.django_db
class TestUserQueries:
def test_user_filter(self):
3)
UserFactory.create_batch(assert User.objects.count() == 3
assert User.objects.filter(username__startswith='user').count() == 3
def test_user_order(self):
= UserFactory.create_batch(3)
users = User.objects.order_by('-date_joined')
ordered assert list(ordered) == sorted(users, key=lambda x: x.date_joined, reverse=True)
Client Testing
URL Testing
def test_homepage(client):
= client.get('/')
response assert response.status_code == 200
assert 'Welcome' in str(response.content)
def test_protected_view(client, user):
client.force_login(user)= client.get('/protected/')
response assert response.status_code == 200
def test_post_request(client):
= client.post('/submit/', {
response 'title': 'Test',
'content': 'Content'
})assert response.status_code == 302 # redirect after success
Template Testing
def test_template_rendering(client):
= client.get('/profile/')
response assert 'profile.html' in [t.name for t in response.templates]
assert 'Profile Page' in str(response.content)
Authentication Testing
Login Testing
@pytest.mark.django_db
class TestAuth:
def test_login(self, client, user, user_data):
= client.post('/login/', {
response 'username': user_data['username'],
'password': user_data['password']
})assert response.status_code == 302
assert '_auth_user_id' in client.session
def test_logout(self, client, user):
client.force_login(user)= client.get('/logout/')
response assert '_auth_user_id' not in client.session
Permission Testing
from django.contrib.auth.models import Permission
@pytest.mark.django_db
def test_user_permissions(user):
= Permission.objects.get(codename='add_user')
permission
user.user_permissions.add(permission)assert user.has_perm('auth.add_user')
Form Testing
Form Validation
from myapp.forms import UserProfileForm
def test_valid_form():
= UserProfileForm(data={
form 'name': 'John Doe',
'email': 'john@example.com',
'bio': 'Test bio'
})assert form.is_valid()
def test_invalid_form():
= UserProfileForm(data={})
form assert not form.is_valid()
assert 'name' in form.errors
File Upload Testing
import tempfile
from django.core.files.uploadedfile import SimpleUploadedFile
def test_file_upload(client, user):
client.force_login(user)with tempfile.NamedTemporaryFile() as tmp:
b'test content')
tmp.write(0)
tmp.seek(= client.post('/upload/', {
response 'file': SimpleUploadedFile(tmp.name, tmp.read())
})assert response.status_code == 302
API Testing
REST Framework Testing
from rest_framework.test import APIClient
import pytest
@pytest.fixture
def api_client():
return APIClient()
@pytest.mark.django_db
class TestUserAPI:
def test_list_users(self, api_client, admin_user):
api_client.force_authenticate(admin_user)= api_client.get('/api/users/')
response assert response.status_code == 200
assert len(response.json()) > 0
def test_create_user(self, api_client, admin_user):
api_client.force_authenticate(admin_user)= api_client.post('/api/users/', {
response 'username': 'newuser',
'email': 'new@example.com',
'password': 'secret123'
})assert response.status_code == 201
Mocking
Basic Mocking
from unittest.mock import patch
def test_external_api_call():
with patch('requests.get') as mock_get:
= 200
mock_get.return_value.status_code = {'data': 'test'}
mock_get.return_value.json.return_value # Test your function that uses requests.get
assert your_function() == expected_result
@patch('myapp.services.external_api.make_request')
def test_service(mock_request):
= {'status': 'success'}
mock_request.return_value # Test your service
Email Mocking
from django.core import mail
def test_send_email(client):
= client.post('/send-email/')
response assert len(mail.outbox) == 1
assert mail.outbox[0].subject == 'Expected Subject'
Best Practices
1. Use Fixtures Effectively
- Keep fixtures focused and small
- Use factory boy for complex object creation
- Share fixtures in conftest.py
2. Test Organization
@pytest.mark.django_db
class TestUser:
"""Group related tests in classes"""
def test_create(self):
# test user creation
pass
def test_update(self):
# test user update
pass
3. Parametrize Tests
@pytest.mark.parametrize('username,expected', [
'valid_user', True),
('inv@lid', False),
('', False),
(
])def test_username_validation(username, expected):
= UserForm(data={'username': username})
form assert form.is_valid() == expected
4. Use Markers
@pytest.mark.slow
def test_expensive_operation():
# long running test
pass
# Run with: pytest -m "not slow"
5. Debug Tips
def test_with_debug(client):
= client.get('/view/')
response import pdb; pdb.set_trace() # Debug point
# or use pytest --pdb
6. Coverage
pytest --cov=myapp
pytest --cov=myapp --cov-report=html
7. Configuration Best Practices
# conftest.py
import pytest
from django.conf import settings
@pytest.fixture(autouse=True)
def media_storage(settings, tmpdir):
= tmpdir.strpath
settings.MEDIA_ROOT
@pytest.fixture
def enable_debug(settings):
= True settings.DEBUG
Remember to: - Write tests first (TDD when possible) - Keep tests simple and focused - Use meaningful test names - Test edge cases and error conditions - Use appropriate assertions - Keep test data minimal - Clean up after tests - Use continuous integration
Common Testing Scenarios
1. Testing Signals
@pytest.mark.django_db
def test_profile_signal():
= User.objects.create_user(username='test')
user assert hasattr(user, 'profile')
assert user.profile is not None
2. Testing Management Commands
from django.core.management import call_command
from io import StringIO
def test_command_output():
= StringIO()
out 'my_command', stdout=out)
call_command(assert 'Expected output' in out.getvalue()
4. Testing Middlewares
def test_middleware(client):
= client.get('/')
response assert response['Custom-Header'] == 'Expected Value'
5. Testing Admin
from django.contrib.admin.sites import AdminSite
from myapp.admin import UserAdmin
from myapp.models import User
@pytest.mark.django_db
def test_admin_view(admin_client):
= admin_client.get('/admin/myapp/user/')
response assert response.status_code == 200
def test_admin_action():
= AdminSite()
site = UserAdmin(User, site)
user_admin # Test admin actions