## The fixture duplication problem
As your test suite grows across multiple files, you'll want the same fixtures in several places — a `sample_user`, a database connection, an auth token. Duplicating them is a maintenance burden; importing them from another test file is awkward and breaks pytest's discovery model.
`conftest.py` solves this cleanly. It is a special file that pytest discovers automatically. Any fixture defined there is available to all tests in the same directory and all subdirectories — **with no import required**.
## How pytest discovers conftest.py
When pytest collects a test at `tests/unit/test_users.py`, it loads every `conftest.py` it finds from the rootdir down to the test file's directory:
```
project/
├── conftest.py ← loaded 1st (project-wide fixtures)
└── tests/
├── conftest.py ← loaded 2nd (test-suite fixtures)
└── unit/
├── conftest.py ← loaded 3rd (unit-test fixtures)
└── test_users.py ← the test file
```
Fixtures at each level are available to all tests below that level. The test file never imports from conftest — pytest injects fixtures by parameter name automatically.
## Multiple conftest files and override precedence
If the same fixture name appears in multiple conftest files, the **nearest** one wins — the one closest to the test file. This lets you define a sensible default at the root and override it for a specific subtree:
```
project/
├── conftest.py # base_url = 'https://prod.example.com'
└── tests/
├── conftest.py # base_url = 'https://staging.example.com' ← overrides for tests/
└── local/
└── conftest.py # base_url = 'http://localhost:8000' ← overrides for local/
```
Tests in `tests/local/` use `localhost`; everything else in `tests/` uses staging.
## Fixture scope in conftest
Scope works the same as in-file fixtures, but session-scoped fixtures in conftest are especially powerful — they run once for the entire pytest session:
```python
# conftest.py
import pytest
import json
@pytest.fixture(scope='session')
def product_catalog():
with open('tests/fixtures/products.json') as f:
return json.load(f) # loaded once; shared by every test that needs it
```
This is the right home for expensive setup: authenticated HTTP sessions, database connections, loading large datasets. Run them once, share the result.
## Scope lifetimes
| Scope | Created | Destroyed | Best for |
|-------|---------|-----------|---------|
| `function` (default) | before each test | after each test | isolated state per test |
| `class` | before first test in class | after last test in class | class-level shared setup |
| `module` | before first test in file | after last test in file | file-level resources |
| `session` | before first test of run | after last test of run | expensive global setup |
## conftest is not a module you import
Never write `from conftest import my_fixture`. pytest handles discovery and injection automatically. Importing from conftest bypasses this mechanism and creates subtle bugs.
## yield fixtures for teardown
`yield` in a fixture separates setup from teardown. The code after `yield` runs when the fixture's scope ends — even if the test fails:
```python
@pytest.fixture(scope='session')
def db_connection():
conn = open_connection()
yield conn # test code runs here; conn is injected
conn.close() # runs after the last test that uses this fixture
```
**Discovery:** pytest loads `conftest.py` from rootdir down to the test file's directory. No import needed — fixtures are injected by parameter name.
**Override precedence:** nearest conftest wins. `tests/unit/conftest.py` overrides `tests/conftest.py` for tests inside `tests/unit/`.
**Fixture scope lifetimes:**
| Scope | `setup` runs | `teardown` runs |
|-------|-------------|----------------|
| `function` | before each test | after each test |
| `class` | before first method | after last method |
| `module` | before first test in file | after last test in file |
| `session` | once per `pytest` run | at the very end |
**yield fixture (setup + teardown):**
```python
@pytest.fixture(scope='session')
def resource():
obj = setup()
yield obj # tests receive obj
teardown(obj) # runs when scope ends
```
**Fixture depending on another fixture:**
```python
@pytest.fixture
def auth_headers(auth_token): # auth_token is another fixture
return {'Authorization': f'Token {auth_token}'}
```
**Indirect parametrize:**
```python
@pytest.fixture
def user(request):
return create_user(role=request.param)
@pytest.mark.parametrize('user', ['admin', 'viewer'], indirect=True)
def test_access(user): ...
```
**Never** `from conftest import fixture` — pytest handles it automatically.
Create `conftest.py` at the project root with a `sample_users` fixture that returns a list of three user dicts (each with `"name"` and `"email"` keys). Create `test_users_a.py` with a test that asserts `len(sample_users) == 3`, and `test_users_b.py` with a test that asserts each user dict contains the `"email"` key. Neither test file should import anything from `conftest.py`. Run `pytest -v` and confirm both tests pass.
# conftest.py
import pytest
@pytest.fixture
def sample_users():
# return a list of 3 dicts, each with 'name' and 'email'
pass
# test_users_a.py
def test_users_count(sample_users):
# assert len(sample_users) == 3
pass
# test_users_b.py
def test_users_have_email(sample_users):
# assert 'email' in each user
pass
Solution
# conftest.py
import pytest
@pytest.fixture
def sample_users():
return [
{'name': 'Alice', 'email': '[email protected]'},
{'name': 'Bob', 'email': '[email protected]'},
{'name': 'Carol', 'email': '[email protected]'},
]
# test_users_a.py
def test_users_count(sample_users):
assert len(sample_users) == 3
# test_users_b.py
def test_users_have_email(sample_users):
for user in sample_users:
assert 'email' in user
Create `settings.json` with `{"debug": true, "page_size": 10}`. Create `conftest.py` with a `session`-scoped `config` fixture that reads this file and prints `"Loading config"` before returning the parsed dict. Write two test functions in two separate files — both use `config`. Run `pytest -v -s` and verify `"Loading config"` appears exactly once in the output, not twice.
Create a root `conftest.py` with a `base_url` fixture returning `"https://apilearn.tukas.dev"`. Create a `local/` subdirectory with its own `conftest.py` that overrides `base_url` to `"http://localhost:8000"`. Write `test_root.py` at the root and `local/test_local.py` inside the subdirectory — each should print `base_url` with `-s`. Run `pytest -v -s` and confirm the correct URL appears in each test.
Create a `conftest.py` with a `scaled` fixture that reads `request.param` (a number) and returns `request.param * 10`. Write a parametrized test using `indirect=True` with params `[2, 5, 7]`. Each test run should receive `20`, `50`, and `70` respectively — assert this. Run `pytest -v` and confirm three separate test cases appear in the output.
# conftest.py
import pytest
@pytest.fixture
def scaled(request):
# return request.param multiplied by 10
pass
# test_scale.py
import pytest
@pytest.mark.parametrize('scaled', [2, 5, 7], indirect=True)
def test_scaled_value(scaled):
# assert scaled in (20, 50, 70) depending on which param was passed
pass
Create a `conftest.py` with a `temp_file` fixture that: (1) creates a `Path("test_output.txt")` and writes `"hello from fixture"` to it, printing `"SETUP"`, then (2) yields the path, then (3) deletes the file and prints `"TEARDOWN"`. Write a test that reads the file and asserts its contents. Run `pytest -v -s` to see SETUP and TEARDOWN appear around the test.
# conftest.py
import pytest
from pathlib import Path
@pytest.fixture
def temp_file():
path = Path('test_output.txt')
# write 'hello from fixture' to path, print 'SETUP'
# yield path
# delete the file, print 'TEARDOWN'
pass
# test_tempfile.py
def test_reads_fixture_file(temp_file):
# read temp_file and assert contents == 'hello from fixture'
pass
We use necessary cookies to run the site. With your permission, we can also save your site preferences and use analytics and advertising cookies to understand usage and support the project.
Open tools in tabs.Exercises, IDE tools, and trainers stay available as site tabs.
Switch without losing context.Move between explanations, code, and utilities while keeping your place.
Use the sidebar as your map.The left panels hold navigation, settings, files, libraries, and tool controls.
PythonJavaScriptSQLite
One IDE, three practical modes
Python in the browser.Run small scripts, try libraries, and practice API requests without installing anything.
JavaScript for quick experiments.Test browser-friendly code and compare ideas next to your learning materials.
SQLite for data practice.Open the database explorer to inspect tables, write queries, and learn SQL workflows locally.
TopicIDE
Work side by side with split tabs
Keep instructions visible.Open an exercise or reference page beside the IDE instead of jumping back and forth.
Compare tools while you learn.Place regex checks, explanations, and code experiments next to each other when the task needs it.
Close the split when you are done.The workspace returns to a single focused tab, and your open site tabs remain available.
Code Typing Trainer
Or plain text
This trainer is designed for a physical keyboard.Open this section on a laptop or desktop with a wide screen. Touch typing practice will not work correctly on a phone.
Speed: 0 chars/min
0 words/min
Best speed (60s): 0 chars/min
0 words/min
Errors: 0
Total time: 0.0 s
To practice touch typing, avoid looking at your physical keyboard.