Pytest Basics and Fixtures

RoadmapsPython Backend

Scenario

You have a massive test file with 100 lines of setup (database connection, user creation) copied and pasted at the top of every single test function. It's brittle and slow.

Mental model

Fixtures are dependency injection for your tests. Just like FastAPI's `Depends`, a test simply declares what it needs, and pytest figures out how to build it, pass it in, and clean it up.

Deep dive

Pytest is the standard testing framework for Python. Instead of subclassing `unittest.TestCase`, you write plain `assert` statements. Pytest introspects the assertions to provide rich failure diffs.

A `fixture` is a setup function decorated with `@pytest.fixture`. When a test function includes the fixture's name in its arguments, pytest executes the fixture first and injects the returned value.

Fixtures use `yield` instead of `return` to handle teardown. Everything before `yield` is setup; everything after `yield` runs after the test finishes, ensuring resources like database connections are closed even if the test fails.

Code examples

With Pytest Fixtures (Clean)

import pytest

@pytest.fixture
def db_session():
    # Setup: runs before test
    db = setup_test_db()
    yield db
    # Teardown: runs after test
    teardown_test_db(db)

def test_create_user(db_session):
    # db_session is injected automatically
    user = create_user(db_session, 'alice')
    assert user.name == 'alice'

The fixture handles the lifecycle. The test only contains the actual logic. Pytest guarantees the code after `yield` runs.

Common mistakes

Recall questions

Questions & answers

How would you share a database connection fixture across hundreds of tests without recreating the engine every time?

Use a fixture with `scope='session'` to create the engine once. Then, use a `scope='function'` fixture that connects to the engine, starts a transaction, `yield`s the session, and rolls back the transaction in the teardown so tests don't see each other's data.

If test A and test B both require a `user` fixture, does test B get the exact same user object?

By default, fixtures are function-scoped. Pytest will execute the fixture twice, so test B gets a freshly created user object, preventing test pollution.

Continue learning

Next: Parametrized Tests

Next: Mocking and Patching

Return to Python Backend Roadmap