skip to content

With pytest-django, why does a plain test function that queries a Django model fail with 'Database access not allowed', and how do you fix it?

level: juniorimportance: must knowfreq 55%

answer

  1. blocked by default
  2. a marker or a fixture
  3. opt in per test
  4. rolled back like TestCase

basics

~10 s

pytest-django blocks database access unless a test opts in, so an unmarked query raises RuntimeError. Add @pytest.mark.django_db or request the db fixture; the test then runs in a transaction that is rolled back afterwards.

solid answer

~40 s

pytest-django deliberately makes the database unreachable by default: it patches Django's connection setup so any query raises `RuntimeError: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it.` A test opts in either with `@pytest.mark.django_db` (on the function, a class, or a module through `pytestmark`) or by requesting the `db` fixture, directly or through another fixture such as `admin_user` or `django_user_model`. The first such test triggers the session-scoped `django_db_setup`, which creates and migrates the test database. Each opted-in test then runs inside a transaction that is rolled back at the end, the same isolation Django's `TestCase` gives. Tests that never opt in stay database-free and fast, which is the point of the default.

code

python · 13 lines
python
import pytest

from orders.models import Order


@pytest.fixture
def pending_order(db):  # requesting db opts every user in
    return Order.objects.create(total_cents=500)


def test_cancel(pending_order):
    pending_order.cancel()
    assert pending_order.status == "cancelled"

go deeper

for a junior

Remember that pytest-django blocks the database until a test uses the django_db marker or the db fixture.

for a middle

Explain how opting in triggers django_db_setup once per session and wraps each test in a rolled-back transaction, and which fixtures request db for you.

for a senior

Use the block as a design tool: keep most tests database-free, put db on data fixtures, and catch accidental ORM use in unit tests.

for a principal

Set suite conventions for marking database tests so the split between fast unit tests and database tests stays visible and intentional.

## Why the default is "no database" Under Django's own runner, any `TestCase` or `TransactionTestCase` can touch the database and `SimpleTestCase` refuses queries. pytest has no base classes, so **pytest-django** needs another way to know which tests need a database. Its answer is to **block database access by default** and make each test opt in. The block is implemented by patching the method Django uses to open a connection. Any query from a test, or from a fixture it uses, that has not opted in raises: ```text RuntimeError: Database access not allowed, use the "django_db" mark, or the "db" or "transactional_db" fixtures to enable it. ``` The benefits are deliberate: - **Speed.** A suite of pure-logic tests never creates a database at all. - **Explicitness.** Reading a test tells you whether it touches the database. - **Catching surprises.** A "unit" test that quietly queries the ORM fails loudly instead of silently becoming an integration test. ## The two ways to opt in 1. **The marker.** `@pytest.mark.django_db` on a test function or a test class, or `pytestmark = pytest.mark.django_db` at module level. 2. **The `db` fixture.** Request it directly, or indirectly through a fixture that depends on it. `django_user_model`, `admin_user` and `admin_client` all request `db`, so a test using them has database access without a marker. ```python import pytest from orders.models import Order @pytest.mark.django_db def test_new_order_is_pending(): order = Order.objects.create(total_cents=500) assert order.status == "pending" def test_order_count(db): assert Order.objects.count() == 0 ``` ## What happens once a test opts in - The session-scoped **`django_db_setup`** fixture runs on first use: it creates the test database (the usual `test_` prefix), applies migrations, and keeps it for the rest of the session. - The test itself is wrapped in a **transaction that is rolled back** when it ends, which is the isolation Django's `TestCase` provides. Rows a test creates are invisible to the next test. - For real commits, use `@pytest.mark.django_db(transaction=True)` or the `transactional_db` fixture, which behave like `TransactionTestCase` and are slower. | Test declares | Database access | Isolation | |---|---|---| | nothing | blocked, `RuntimeError` on query | n/a | | `@pytest.mark.django_db` or `db` | allowed | per-test transaction rolled back | | `django_db(transaction=True)` or `transactional_db` | allowed | real commits, tables flushed afterwards | ## Common causes of the error in real suites - A **fixture** creates model rows but neither it nor the test requests `db`. Add `db` to the fixture's parameters so every user of the fixture opts in. - A test uses the **`client` fixture** and the view queries the database. `client` itself does not request `db`, unlike `admin_client`, so the test still needs the marker. - Code runs a query at **import time** or in a module-level fixture; move it into a function-scoped fixture that requests `db`. - A module-level `pytestmark` was forgotten when tests were moved to a new file. Existing Django `TestCase` classes collected by pytest are not affected: pytest-django recognises them and unblocks the database for them automatically, which is what lets a suite migrate to plain functions gradually. ## Fixtures that opt in for you Several built-in fixtures request `db` themselves, which is why some tests work without a marker and others do not: | Fixture | Requests `db`? | Why | |---|---|---| | `client`, `async_client` | no | just a `django.test.Client` / `AsyncClient` | | `rf`, `async_rf` | no | builds requests only | | `settings` | no | overrides settings only | | `django_user_model` | yes | the user model is usually queried next | | `admin_user`, `admin_client` | yes | they create or fetch a superuser | | `transactional_db`, `live_server` | yes, transactional | real commits are needed | The practical rule: put `db` in the parameter list of every custom fixture that creates model rows, and mark tests that query through views or services directly. ## How the database gets created `django_db_setup` wraps Django's own test-database setup, so the database is named and migrated exactly as `manage.py test` would do it. pytest-django adds its own switches: `--reuse-db` keeps the database between runs, `--create-db` forces recreation, and `--no-migrations` builds tables straight from the models. Since pytest-django 4.11 the plugin also skips database setup entirely when no collected test asks for a database.

  • A test uses pytest-django's client fixture to GET a page whose view queries the database; why does it still fail with 'Database access not allowed'?
    The `client` fixture just returns a `django.test.Client` and does not request `db`, so the view's query is blocked. Add `@pytest.mark.django_db` or request `db`. By contrast `admin_client` depends on `admin_user`, which requests `db`, so tests using it already have access.
  • How do you enable database access for every test in one module with pytest-django?
    Set `pytestmark = pytest.mark.django_db` at module level; pytest applies the marker to every test in the file. Use `pytestmark = pytest.mark.django_db(transaction=True)` only if the whole module genuinely needs real commits, since that is slower.

saying these in an interview costs you the question

  • Believing pytest-django gives every test database access by default
  • Thinking the django_db marker commits data that later tests can see
  • Fixing the error by pointing tests at the development database
  • Assuming the client fixture already requests the db fixture
  • Saying Django TestCase classes also need the marker under pytest