Skip to content

pytest Fixtures

A pytest fixture is a function decorated with @pytest.fixture that provides setup (and optional teardown) for tests. Any test function that names the fixture as a parameter automatically receives whatever the fixture returns โ€” pytest resolves and calls fixtures for you based on the parameter name, no manual setup/teardown calls needed in each test.

Without fixtures, shared setup gets copy-pasted into every test, or bundled into a setUp/tearDown pair that runs for every test in a class whether it needs that setup or not:

import unittest
class TestOrders(unittest.TestCase):
def setUp(self):
self.db = connect_to_test_db() # runs before EVERY test method
def tearDown(self):
self.db.close()
def test_create_order(self):
order = self.db.create_order(...)
assert order.id is not None
import pytest
@pytest.fixture
def db():
connection = connect_to_test_db()
yield connection # this is what gets injected into the test
connection.close() # runs after the test โ€” teardown
def test_create_order(db): # pytest sees the 'db' parameter, resolves the fixture
order = db.create_order(...)
assert order.id is not None
def test_something_else(): # doesn't need 'db' at all โ€” doesn't pay for that setup
assert 1 + 1 == 2
  • Opt-in per test: only tests that actually declare a fixture as a parameter run its setup โ€” unlike class-wide setUp, unrelated tests arenโ€™t slowed down by setup they donโ€™t need.
  • Composable: fixtures can depend on other fixtures (just by naming them as parameters), building up shared setup in layers instead of one monolithic setUp.
  • yield gives clean teardown: code after yield runs after the test finishes (pass or fail), the same guarantee a finally block gives, without writing try/finally by hand in every test.
  • Fixture scope controls how often itโ€™s recreated: @pytest.fixture(scope="function") (default โ€” once per test), "module" (once per file), "session" (once for the whole test run) โ€” useful for expensive setup (e.g. a real database connection) that doesnโ€™t need to be rebuilt for every single test.
  • Fixtures are matched to tests purely by parameter name โ€” def test_x(db): works because a fixture literally named db exists and is visible (defined in the same file or in a conftest.py).
  • conftest.py is pytestโ€™s mechanism for sharing fixtures across multiple test files without importing them explicitly โ€” any fixture defined there is automatically available to tests in that directory and below.
  • A fixture using return instead of yield has no teardown phase โ€” use yield specifically when thereโ€™s cleanup to run after the test.
@pytest.fixture(scope="session")
def expensive_resource():
resource = build_expensive_thing() # built once for the entire test run
yield resource
resource.cleanup()
  1. How does pytest know to inject a fixture into a specific test function?

    AnswerBy matching the test function's parameter name to a fixture of the same name โ€” `def test_x(db):` pulls in the fixture named `db`.
  2. Whatโ€™s the difference between a fixture that returns a value and one that yields it?

    Answer`yield` allows code after it to run as teardown once the test finishes; `return` provides the value with no teardown phase.
  3. Why would you set a fixtureโ€™s scope to "session" instead of the default?

    AnswerTo avoid recreating expensive setup (like a real database connection) for every single test โ€” a session-scoped fixture is created once and reused across the entire test run.