Pytest Django Tutorial: How to Test Django Applications
Set up pytest-django, configure Django settings, and choose the right fixtures and database mode for reliable application tests.
To test a Django application with pytest, install pytest-django, tell pytest which settings module to use, and run pytest. Tests that use the ORM must explicitly request database access with @pytest.mark.django_db or a database fixture. Use the ordinary rollback-based mode for most tests; choose transactional mode when you need real transaction behavior or a live server.
1. Install pytest-django and configure the project
Install the plugin in the same environment as the Django project. The optional [django] extra also installs Django as a dependency, which can be useful when setting up a new environment. pytest-django integrates with pytest and can collect many existing Django and Nose-style test suites with little or no additional setup. See the official getting-started guide.
python -m pip install pytest-django
# Or ensure Django is installed as a dependency too:
python -m pip install 'pytest-django[django]'
Configure the settings module that should load during tests. Replace yourproject.settings with the import path for your project.
Option A: pytest.ini
[pytest]
DJANGO_SETTINGS_MODULE = yourproject.settings
# Add these only if your app uses Django's default test file names:
python_files = tests.py test_*.py *_tests.py
Option B: pyproject.toml
[tool.pytest.ini_options]
DJANGO_SETTINGS_MODULE = "yourproject.settings"
python_files = ["tests.py", "test_*.py", "*_tests.py"]
pytest versions differ in their native TOML configuration syntax. Use the format supported by the pytest version installed in the project; the current pytest-django documentation describes native [tool.pytest] syntax for pytest 9 and the [tool.pytest.ini_options] format for pytest 7 and 8. Avoid adding a second configuration file with conflicting settings.
Other settings configuration options
- Environment variable: set
DJANGO_SETTINGS_MODULE=yourproject.settingsin the shell or CI environment. - Command line: run
pytest --ds=yourproject.settings. - Configuration file: keep settings in pytest.ini, tox.ini, or the supported pyproject.toml syntax.
Use the project’s test settings module when it differs from local development settings. It can define a test database and test-specific configuration. pytest-django can often locate a project from its manage.py; if imports still fail, inspect the working directory and Python path before adding path changes.
2. Write a first test
Pytest discovers files matching its configured patterns, and test functions whose names begin with test_. Here is a minimal test that does not need database access:
# tests/test_health.py
def test_health_check_returns_ok(client):
response = client.get("/health/")
assert response.status_code == 200
The client fixture makes an in-process Django request and returns the response. It is appropriate for checking status codes, response content, redirects, and view behavior without starting a web server. Run the suite from the project environment with:
pytest
Useful focused invocations include pytest tests/test_health.py for one file, pytest -k health for matching test names, and pytest -q for less verbose output. These are pytest options and do not change Django’s database rules.
3. Decide whether a test needs database access
pytest-django blocks database access by default so database-dependent tests are explicit. Mark the test with django_db or request db when the test or one of its fixtures uses the ORM. The database guide documents the available modes.
Use rollback-based database access for ordinary model tests
import pytest
from django.contrib.auth import get_user_model
@pytest.mark.django_db
def test_user_can_be_created():
User = get_user_model()
user = User.objects.create_user(username="sam", password="secret")
assert user.pk is not None
assert User.objects.filter(username="sam").exists()
Each ordinary database-enabled test is isolated using rollback-based behavior comparable to Django’s TestCase. Alternatively, request the db fixture as a function argument:
def test_user_count(db, django_user_model):
django_user_model.objects.create_user(username="sam", password="secret")
assert django_user_model.objects.count() == 1
Use django_user_model in reusable app tests rather than importing Django’s built-in User class. It follows the project’s configured user model.
Choose transactional mode when transactions matter
import pytest
@pytest.mark.django_db(transaction=True)
def test_transaction_sensitive_behavior():
# Exercise code that depends on actual commit/rollback behavior.
...
The transaction=True mode is comparable to Django’s TransactionTestCase. It permits tests of real transaction boundaries and uses database flushing for isolation, so setup is slower than ordinary rollback-based tests. The transactional_db fixture requests the same general mode. Don’t enable it for every test without a reason.
Use the right database selection
For a project with more than one configured database, the marker accepts a databases argument. Without it, only the default database is requested.
@pytest.mark.django_db(databases=["default", "analytics"])
def test_reads_both_databases():
...
@pytest.mark.django_db(databases="__all__")
def test_uses_every_configured_database():
...
Request only the databases that the test needs. This makes accidental cross-database use easier to spot.
4. Use Django fixtures for common test tasks
pytest-django provides fixtures for requests, settings, users, and live-server tests. The helper reference lists additional details.
| Fixture | Use it for | Notes |
|---|---|---|
client |
In-process synchronous request/response tests. | No separate web server is started. |
async_client |
Requests through Django’s async test client. | Use when the behavior under test needs the async client. |
settings |
Changing Django settings for one test. | Changes are reverted automatically. |
django_user_model |
Tests compatible with custom user models. | Use its manager to create users. |
rf / async_rf |
Constructing a request directly for a view or callable. | Use when a full client request is unnecessary. |
live_server |
Testing through an actual HTTP client against a background Django server. | Requires transactional database behavior. |
Override settings safely
def test_feature_flag(settings):
settings.FEATURE_ENABLED = True
assert settings.FEATURE_ENABLED
The fixture restores setting changes after the test, helping prevent state from leaking into other tests.
Test through a live server only when needed
import requests
def test_homepage_over_http(live_server):
response = requests.get(f"{live_server.url}/", timeout=5)
assert response.status_code == 200
live_server runs Django in a background thread, so the test and server cannot share one transaction. It therefore triggers transactional database access. If the test depends on data established by data migrations, consult pytest-django’s helper guidance about serialized rollback. A client test is simpler and usually faster when in-process request behavior is enough.
5. Organize and run the suite
- Put tests beside an app or in a dedicated test package, following the repository’s existing convention.
- Use descriptive
test_function names and keep each test focused on one behavior. - Mark only tests that need ORM access. Prefer ordinary
django_dbmode unless transaction behavior is under test. - Run
pytestlocally, then use the same command and settings configuration in CI. - When debugging a failure, narrow the run to a file or test with
pytest path/to/test.py::test_name.
For application-level visual checks, a test can also request a rendered page and compare its behavior or generated output. Browser-based screenshot capture is a separate concern from pytest’s Django fixtures; avoid making a test depend on an external website when a local response or deterministic fixture can cover the behavior.
6. Reuse the test database and handle schema changes
By default the test database is set up when first needed and cleaned according to the requested database mode. For repeated local runs, --reuse-db keeps and reuses the test database:
pytest --reuse-db
After model or migration schema changes, recreate it with --create-db:
pytest --reuse-db --create-db
The --no-migrations option (also documented as --nomigrations) builds the test schema by inspecting models rather than applying migrations. This can suit some local workflows, but it does not exercise migration application and can diverge from production schema behavior. Use --migrations to force migrations on where needed. Check the official database documentation for behavior in your installed version.
7. Troubleshooting common failures
| Symptom | Likely cause | Fix |
|---|---|---|
pytest-django cannot find Django settings or reports an import error. |
The settings import path is wrong, pytest runs from an unexpected directory, or the project package is not on Python’s import path. | Check DJANGO_SETTINGS_MODULE, run from the project root, verify package names, or pass --ds=yourproject.settings. |
| Tests are not collected. | The file or function does not match pytest’s discovery patterns. | Use a test_*.py filename and test_ function name, or deliberately configure python_files for the repository’s convention. |
| Database access is blocked. | The test or a fixture touched the ORM without opting into database access. | Add @pytest.mark.django_db to the test, or request db in the relevant fixture/test. |
| Tables or migrated data are missing. | The test database schema/data setup does not match assumptions, migrations are disabled, or reuse preserved an outdated database. | Check migration settings, then recreate with pytest --create-db; account explicitly for data migrations if a test depends on them. |
| A live-server test cannot see data created by the test. | The server and test run in different threads and cannot share one transaction. | Use the transactional behavior required by live_server and create fixtures after the test database is prepared. |
| Tests pass alone but fail in a suite. | Tests may share mutable state, override settings without cleanup, or depend on execution order. | Use the settings fixture, isolate database access correctly, and remove ordering assumptions. |
| Database tests are unexpectedly slow. | Many tests may request transactional flushing or repeatedly recreate the database. | Use ordinary rollback mode when valid, reuse the test database locally, and reserve transactional tests for cases that need them. |
| Custom user-model tests fail in another project. | The test imports Django’s default user model directly. | Use django_user_model fixture or resolve the configured model with get_user_model(). |
8. Performance, reliability, and cost
Test runtime depends on the project, database backend, migrations, fixtures, and test behavior; there is no single reliable speed figure. Keep tests independent, avoid database setup when it is unnecessary, and select ordinary or transactional isolation based on what the test verifies. Database reuse reduces repeated setup work in local repeat runs, while recreation is the safer choice after schema changes. CI should use a predictable test database and should not depend on state retained from earlier jobs.
pytest and pytest-django are Python packages; their installation and test database costs depend on the environment and backend you choose. Live-server tests add server and transactional setup. They are useful for actual HTTP integration checks, but an in-process client test avoids that extra machinery when it covers the same behavior.
Or skip the browser setup
If your Django workflow needs screenshots of pages, ScreenshotNeo provides a one-call website screenshot API. See the API documentation for options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the screenshot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers report the page verdict and billing status. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 shots. Learn about ScreenshotNeo or sign up for 1,000 free screenshots a month with no card.
FAQ
Can pytest-django run existing Django tests?
Often yes. The plugin documentation says standard Django and Nose-style suites can usually be collected with little or no configuration, though project-specific setup may still be needed.
Should every test use the database marker?
No. Mark only tests that access the ORM or depend on fixtures that do so. This keeps database setup explicit.
When should I use the Django test client instead of live_server?
Use client for in-process request/response behavior. Use live_server when the test specifically needs an HTTP server and separate client process or tool.
Does --reuse-db apply migrations after I change a model?
Recreate the database with --create-db after schema changes, as described by pytest-django’s database guide.


