ZhangZhihui's Blog  

In simple terms, Dependency Injection (DI) in Pytest means that your test functions don't have to worry about creating the tools they need; they just "ask" for them by name in their arguments.

Pytest acts as a delivery service: it sees what you've asked for, finds the corresponding fixture, runs it, and "injects" the result into your function.


1. The "Request by Name" Pattern

In standard Python, if a function needs an object, you usually create it inside the function or pass it manually. In Pytest, you simply name the fixture as a parameter.

import pytest

# The "Provider" (The Fixture)
@pytest.fixture
def sample_data():
    return {"id": 1, "name": "Gemini"}

# The "Consumer" (The Test)
def test_data_integrity(sample_data):
    # You never called sample_data(). Pytest did it for you.
    assert sample_data["name"] == "Gemini"

2. Layered Injection (Fixtures requesting Fixtures)

DI becomes really powerful when fixtures start depending on other fixtures. Pytest builds a Dependency Graph to figure out the correct order of execution.

@pytest.fixture
def database_connection():
    return "Connected to DB"

@pytest.fixture
def user_record(database_connection):
    # This fixture 'asks' for the database_connection
    return {"status": database_connection, "user": "Admin"}

def test_user_status(user_record):
    assert user_record["status"] == "Connected to DB"

Pytest resolves this like a puzzle:

  1. Test asks for user_record.

  2. Pytest sees user_record needs database_connection.

  3. Pytest runs database_connection first, then passes the result to user_record, then passes that to the test.


3. The "Setup-Teardown" Injection

DI isn't just about passing data; it’s about managing a lifecycle. When you use yield, Pytest injects the value but keeps the function "alive" to clean up later.

import os

@pytest.fixture
def temp_file():
    # SETUP: Create a file
    f = open("test.txt", "w")
    f.write("Hello World")
    f.close()
    
    yield "test.txt"  # INJECTION: Hand the filename to the test
    
    # TEARDOWN: Delete the file after the test finishes
    if os.path.exists("test.txt"):
        os.remove("test.txt")

def test_file_reading(temp_file):
    with open(temp_file, "r") as f:
        assert f.read() == "Hello World"

4. Why is this useful?

  • Decoupling: Your tests don't need to know how a database is set up or where a temp file is created. They just need the result.

  • Reusability: You can write one client fixture and inject it into 100 different test files.

  • Cleanliness: Your test functions stay focused on assertions (logic) rather than configuration (boilerplate).


Summary of the Flow

  1. Define: You create a function with @pytest.fixture.

  2. Request: You add that function's name as an argument to your test.

  3. Inject: Pytest executes the fixture and swaps the argument name for the actual return value.

 

posted on 2026-03-16 20:07  ZhangZhihuiAAA  阅读(32)  评论(0)    收藏  举报