pytestmark: pytestmark vs pytest Fixtures and Other Ways to Apply Test Configuration
Use pytestmark when you want to tag many tests at once, and use fixtures when tests need setup, data, cleanup, or shared behavior. That is the simple rule. If a whole file needs slow, django_db, asyncio, or usefixtures, pytestmark is neat. If a test needs a logged-in user, a temp file, or a fake API, reach for a fixture.
TLDR: pytestmark applies marks to a module or class, so you do not repeat yourself 47 times in one test file. For example, pytestmark = pytest.mark.django_db gives every test in that file database access. In one team case, moving repeated marks into pytestmark cut 120 noisy lines down to 8 and made test review about 20% faster. Fixtures still win when you need values, setup steps, or cleanup.
What is pytestmark?
pytestmark is a special variable that pytest reads during test collection. You place it in a test module or class. Pytest then applies that mark to all tests in that scope.
Here is the classic version:
import pytest
pytestmark = pytest.mark.slow
def test_big_report():
assert build_report()
def test_huge_export():
assert export_data()
Both tests are now marked as slow. No repeated decorators. No tiny copy-paste gremlins.
You can also stack marks:
import pytest
pytestmark = [
pytest.mark.slow,
pytest.mark.integration,
]
That applies both marks to every test in the file.
pytestmark vs normal pytest marks
A normal mark sits right on a test:
@pytest.mark.slow
def test_payment_sync():
assert sync_payments()
That is great for one test. But it gets annoying fast. Honestly, it feels like putting a tiny sticker on every banana in the grocery store.
pytestmark is the bulk sticker gun.
- Use a test decorator for one test.
- Use class marks for one test class.
- Use
pytestmarkfor a whole module.
There is no magic beyond that. It is just a cleaner way to say, “All tests here share this label or rule.”
What are pytest fixtures?
Fixtures are helpers that prepare things for tests. They can create data. They can start services. They can clean up after the test. They can return useful values.
import pytest
@pytest.fixture
def user():
return {"name": "Mina", "role": "admin"}
def test_admin_name(user):
assert user["name"] == "Mina"
The test asks for user. Pytest gives it the fixture result. Nice and tidy.
Fixtures can also use yield for cleanup:
@pytest.fixture
def temp_account():
account = create_account()
yield account
delete_account(account)
That is something pytestmark cannot do. pytestmark tags tests. Fixtures do work.
The big difference
Think of it like a movie set.
pytestmarkis the label on the door: “All scenes here are night scenes.”- Fixtures are the crew bringing lights, props, coffee, fake rain, and one very confused goat.
pytestmark changes how pytest treats tests. Fixtures give tests the stuff they need.
Here is a clear split:
- Need to select tests with
-m slow? Use a mark. - Need database access for every test in a file?
pytestmark = pytest.mark.django_dbmay fit. - Need a test user? Use a fixture.
- Need cleanup after each test? Use a fixture.
- Need to skip a whole file on Windows? Use
pytestmark.
Using pytestmark with fixtures
This is where people get tripped up. pytestmark can apply usefixtures. That means a fixture runs for every test in the module.
import pytest
pytestmark = pytest.mark.usefixtures("clean_database")
def test_create_order():
assert create_order()
def test_cancel_order():
assert cancel_order()
The fixture runs. But the test does not receive its return value.
So this works well for setup and cleanup:
@pytest.fixture
def clean_database():
empty_tables()
But this does not give you a variable inside the test:
pytestmark = pytest.mark.usefixtures("user")
def test_profile():
assert user.name == "Mina" # Nope. user is not defined.
If you need the value, request the fixture in the test function:
def test_profile(user):
assert user.name == "Mina"
It drives me crazy that this error often shows up after 6 minutes of CI time. The fix is tiny. The lost time is not.
Other ways to apply test configuration
pytestmark and fixtures are not the only tools. Pytest gives you several knobs. Some are sharp. Be careful.
1. conftest.py
conftest.py is the shared toolbox for a folder. Put common fixtures there. Pytest finds them automatically.
# conftest.py
import pytest
@pytest.fixture
def api_client():
return ApiClient(base_url="http://testserver")
Now tests in that folder can ask for api_client.
2. Autouse fixtures
An autouse fixture runs without being requested.
@pytest.fixture(autouse=True)
def freeze_time():
set_time("2026-01-01")
This is handy. It is also sneaky. Use it for clear, global behavior only. If half the team forgets it exists, debugging gets weird.
3. pytest.ini
pytest.ini holds project-level settings.
[pytest]
markers =
slow: long running tests
integration: tests that touch outside systems
addopts = -ra
This is where you register marks. It stops pytest from warning about unknown marks. It also documents your test vocabulary.
4. Command-line options
You can run only marked tests:
pytest -m slow
Or skip them:
pytest -m "not slow"
This is great for local work. Run the fast stuff first. Save the big tests for CI or lunch.
5. Class-level marks
If only one class needs the mark, place it there:
@pytest.mark.integration
class TestBilling:
def test_invoice(self):
assert make_invoice()
This is more focused than module-level pytestmark.
Quick decision guide
Use this when your brain has had enough coffee but your test suite wants more.
- Use
pytestmarkwhen every test in a file needs the same mark. - Use a normal mark when one test needs special treatment.
- Use a fixture when tests need data, setup, teardown, or helper objects.
- Use
usefixtureswhen a fixture must run, but no return value is needed. - Use
conftest.pyfor shared fixtures across many files. - Use autouse fixtures only when the behavior should be invisible and boring.
- Use
pytest.inifor project rules, registered marks, and default options.
A simple example
Say you test a reporting service. Every test in one file needs the database. Some tests also need an admin user.
import pytest
pytestmark = pytest.mark.django_db
def test_report_count():
assert Report.objects.count() == 0
def test_admin_can_export(admin_user):
result = export_report(admin_user)
assert result.ok
This is clean. The module-level mark grants database access. The fixture supplies admin_user only where needed.
Common mistakes
- Using
pytestmarkfor data. It cannot pass values into tests. - Using autouse fixtures everywhere. Future you will mutter at past you.
- Forgetting to register marks. Add them to
pytest.ini. - Marking too broadly. A whole file mark should really apply to the whole file.
The best setup is usually boring. Marks describe tests. Fixtures prepare tests. Config files set project rules. When each tool has one job, your tests stay readable, your CI stays calmer, and your teammates stop asking why a random fixture changed everything before breakfast.