Django’s built-in test runner works, but pytest makes testing more powerful. Better error messages, parametrize for table-driven tests, and a larger plugin ecosystem for coverage, factories, and database handling.
TL;DR: Set up pytest-django to run your Django test suite, add fixtures, use factories for test data, and get coverage reports.
Stack: Python, Django, pytest, pytest-django, factory-boy
Level: Intermediate
Reading time: ~9 min
Setup Django project with DRF
pip install django djangorestframework
django-admin startproject projtest .
python3 manage.py startapp app
Create the Hero model
class Hero(models.Model):
name = models.CharField(max_length=100)
canFly = models.BooleanField()
genre = models.CharField(max_length=10)
Create use case, protocols, and controller
Organize the project in layers: application/use_cases/, infra/, main/, and presentation/. The controller delegates to the use case, and the use case stays free of HTTP concerns.
class ListHeroesController(APIView):
def post(self, request):
try:
inbound = ListHeroesRequest()
inbound.canFly = request.data.get("canFly", "false").lower() == "true"
inbound.genre = request.data.get("genre", "male")
use_case = ListHeroes()
result = use_case.execute(inbound)
outbound = [hero.__dict__ for hero in result]
return Response({"data": outbound}, status=status.HTTP_200_OK)
except Exception as e:
return Response({"message_error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
Install pytest
pip install pytest pytest-django
Create pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = projtest.settings
python_files = tests.py test_*.py *_tests.py
Write tests
# tests/unit/test_list_heroes.py
import pytest
from application.use_cases.list_heroes.list_heroes import ListHeroes
from application.use_cases.list_heroes.protocols.list_heroes_request import ListHeroesRequest
from app.models import Hero
@pytest.mark.django_db
def test_list_heroes_use_case():
Hero.objects.create(name="Superman", canFly=True, genre="male")
Hero.objects.create(name="Wonder Woman", canFly=False, genre="female")
Hero.objects.create(name="Batman", canFly=False, genre="male")
inbound = ListHeroesRequest()
inbound.canFly = True
inbound.genre = "male"
use_case = ListHeroes()
result = use_case.execute(inbound)
assert len(result) == 1
assert result[0].name == "Superman"
assert result[0].canFly is True
pytest
Add coverage
pip install pytest-cov
pytest --cov=application --cov-report=term-missing
Create .coveragerc to exclude protocol files from coverage (they are data containers, not logic):
[run]
omit =
application/use_cases/*/protocols/*
What you’ve built
pytest configured for your Django project with working tests, database fixtures, and coverage reporting.
Next steps
- Use @pytest.mark.django_db on tests that need database access. Without it, database calls raise an error.
- Use factory-boy for test data: UserFactory() is cleaner than repeating model setup in every test.
- Run pytest –reuse-db with pytest-django to skip migrations on repeated test runs, which speeds up the test suite significantly.
Questions or feedback? Find me on LinkedIn or GitHub.