Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

How to Use Fixtures in pytest

Tech 1

Requesting Fixtures

At a fundamental level, test functions request their required dependencies by declaring them directly as function parameters. When pytest executes a test case, it inspects the function signature, locates registered fixtures that match those parameter names, runs the fixture functions, captures their returned values, and injects them as arguments into the test.

Basic Example

Consider the following demonstration using a simplified task-processing workflow:

import pytest


class Task:
    def __init__(self, title: str):
        self.title = title
        self.is_complete = False

    def complete(self):
        self.is_complete = True


class Workflow:
    def __init__(self, *tasks: Task):
        self.items = list(tasks)
        self._process_tasks()

    def _process_tasks(self):
        for item in self.items:
            item.complete()


# Setup
@pytest.fixture
def initial_tasks():
    return [Task("Setup"), Task("Cleanup")]


def test_workflow_execution(initial_tasks):
    # Act
    workflow = Workflow(*initial_tasks)

    # Assert
    assert all(t.is_complete for t in workflow.items)

In this scenario, two classes are defined: Task and Workflow.

  • The Task class represents a single unit of work, tracking its title and completion status. It provides a complete() method to update its state.
  • The Workflow class accepts multiple Task instances during initialization. It automatically triggers the processing step, which marks every provided task as complete.

A fixture named initial_tasks prepares a default list of Task objects. The test function test_workflow_execution reqeusts this fixture. When pytest runs the test, it detects the dependency, executes initial_tasks, and passes the resulting list directly into the test. The test then instantiates the Workflow and verifeis that all tasks transitioned to the completed state.

If executed manually, the underlying flow would resemble:

def setup_initial_tasks():
    return [Task("Setup"), Task("Cleanup")]


def test_workflow_execution(initial_tasks):
    # Act
    workflow = Workflow(*initial_tasks)

    # Assert
    assert all(t.is_complete for t in workflow.items)


# Setup equivalent
tasks = setup_initial_tasks()
test_workflow_execution(initial_tasks=tasks)

Fixtures Depending on Other Fixtures

One of pytest's strongest features is its highly flexible fixture dependency system. Complex setup logic can be broken down into smaller, focused functions where each explicitly states its own requirements. Fixtures can request other fixtures using the exact same parameter-declaration pattern used by tests.

# contents of test_workflow.py
import pytest


# Setup
@pytest.fixture
def seed_value():
    return 10


# Setup
@pytest.fixture
def number_list(seed_value):
    return [seed_value]


def test_list_expansion(number_list):
    # Act
    number_list.append(25)

    # Assert
    assert number_list == [10, 25]

This structure mirrors the previous example. Here, number_list depends on seed_value. When the test requests number_list, pytest resolves the dependency graph: it runs seed_value first, injects its output into number_list, and finally provides the fully constructed list to the test. The request rules for fixtures are identical to those for tests.

Manually, this execution chain translates to:

def provide_seed():
    return 10


def build_list(seed_val):
    return [seed_val]


def test_list_expansion(prepared_list):
    # Act
    prepared_list.append(25)

    # Assert
    assert prepared_list == [10, 25]


# Manual resolution
seed = provide_seed()
collection = build_list(seed_val=seed)
test_list_expansion(prepared_list=collection)

Reusability and Test Isolation

The fixture system shines when defining reusable setup routines that can be shared across multiple tests without introducing state leakage. Each test receives a fresh, independent instance of the requested fixture, guaranteeing that modifications made in one test do not affect another. This ensures consistent and repeatable outcomes across your test suite.

# contents of test_workflow.py
import pytest


# Setup
@pytest.fixture
def seed_value():
    return 10


# Setup
@pytest.fixture
def number_list(seed_value):
    return [seed_value]


def test_append_integer(number_list):
    # Act
    number_list.append(42)

    # Assert
    assert number_list == [10, 42]


def test_append_float(number_list):
    # Act
    number_list.append(3.14)

    # Assert
    assert number_list == [10, 3.14]

Here, both test_append_integer and test_append_float declare number_list as an argument. Behind the scenes, pytest invokes the fixture factory separately for each test. The seed_value fixture also runs twice. This ensures that both tests start from a clean slate, eliminating cross-test pollution.

If handled manually without an automated framework, the necessary repetition would look like this:

def provide_seed():
    return 10


def build_list(seed_val):
    return [seed_val]


def test_append_integer(prepared_list):
    # Act
    prepared_list.append(42)

    # Assert
    assert prepared_list == [10, 42]


def test_append_float(prepared_list):
    # Act
    prepared_list.append(3.14)

    # Assert
    assert prepared_list == [10, 3.14]


# Manual execution for Test 1
s1 = provide_seed()
l1 = build_list(seed_val=s1)
test_append_integer(prepared_list=l1)

# Manual execution for Test 2 (requires full re-setup)
s2 = provide_seed()
l2 = build_list(seed_val=s2)
test_append_float(prepared_list=l2)
Tags: pytest

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.