Fixtures and fixture scope
Overview
A **fixture** is a function that sets up data or state before a test runs. We use the `@pytest.fixture` decorator to mark it. **Scope** controls how often the fixture runs: once per test (the default), or once per entire session. Here is a worked example creating a student dictionary: ```python import pytest @pytest.fixture def student_data(): print('Creating student') return {'name': 'Arjun', 'marks': 85} def test_name(student_data): assert student_data['name'] == 'Arjun' def test_marks(student_data): assert student_data['marks'] == 85 ``` pytest runs `student_data` before `test_name`, and then runs it again before `test_marks`. It provides fresh data every time because its scope is function-level by default. Now, look at this faded example where one step is blank: ```python import pytest @pytest._______ def empty_cart(): return [] def test_cart_empty(empty_cart): assert len(empty_cart) == 0 ``` To turn `empty_cart` into a fixture, we fill in the blank with `fixture`.
Without fixtures, you repeat setup code in every single test. Fixtures keep test files clean and ensure each test gets a fresh, predictable starting state.
Where used: pytest test suites, Database connections in tests
Why learn this
- Reusing setup data across many tests without copy-pasting.
- Speeding up tests by sharing heavy setup (like database connections) using session scope.
Code walkthrough
import pytest
@pytest.fixture
def config():
return {'mode': 'test'}
def test_config(config):
print(config['mode'])
assert config['mode'] == 'test'
test_config({'mode': 'test'})
Focus: def test_config(config):
Aha moment
import pytest
@pytest.fixture
def user():
return {'name': 'Rahul'}
def test_change(user):
user['name'] = 'Amit'
def test_check(user):
print(user['name'])
# Simulating what pytest does:
t1_user = user()
test_change(t1_user)
t2_user = user()
test_check(t2_user)
Prediction: What does test_check print?
Common guess: Amit
It prints 'Rahul'. The default scope is 'function', so pytest creates a completely fresh dictionary for `test_check`. The mutation in `test_change` is forgotten.
Common mistakes
- Forgetting to request the fixture: If your test function does not include the fixture name in its arguments (like `def test_name():`), pytest will not run the fixture and you cannot use its data.
- Mutating shared session fixtures: If a fixture has `scope='session'` and one test changes the data, the next test gets the changed data. Only use session scope for read-only data.
Recall questions
- Explain fixtures and fixture scope in your own words, as if to a classmate.
- What is the default scope of a pytest fixture?
Understanding checks
What does test_two print?
2
The default scope is 'function', so the fixture runs a second time before `test_two`, incrementing the global counter to 2.
How many times does 'Connecting to DB' print when these tests run?
Exactly once.
Because the scope is 'session', pytest runs the fixture only once for the entire test run, and shares the result with both tests.
Practice tasks
Set the scope
Given this code, change the fixture so it only runs once per test session.
Challenge
Create and use a fixture
Create a fixture named `my_list` that returns `[1, 2]`. Then write a test function named `test_list` that requests the fixture and asserts its length is 2.
Continue learning
Previous: Test discovery and naming conventions