backend / python core / 15_pytest_fixture_patch.md

Fixtures and Patch in pytest

3 interview angles 3 min read source

Fixtures and Patch in pytest

What is a Fixture in pytest?

A fixture in pytest is a powerful and flexible mechanism used to set up and tear down resources required for testing. Fixtures are functions that can provide test data, prepare environments, or perform initialization and cleanup tasks. They are used to avoid repetitive setup code and make tests more modular and maintainable.

Key Features of Fixtures

  1. Reusability: Fixtures can be reused across multiple test functions.
  2. Scope: Fixtures can be scoped to different levels (function, class, module, package, or session).
  3. Dependency Injection: Fixtures can depend on other fixtures to build complex setups.
  4. Automatic Cleanup: Cleanup actions can be added using yield or finalizer methods.

Example of a Fixture

import pytest

# Define a fixture
@pytest.fixture
def sample_data():
    return {"name": "Danil", "role": "developer"}

# Use the fixture in a test
def test_data_format(sample_data):
    assert sample_data["name"] == "Danil"
    assert sample_data["role"] == "developer"

Fixture Scope

The scope of a fixture defines how often the fixture is invoked:

  • function: Default scope; the fixture runs once per test function.
  • class: The fixture runs once per test class.
  • module: The fixture runs once per test module.
  • session: The fixture runs once for the entire test session.
@pytest.fixture(scope="module")
def db_connection():
    connection = connect_to_db()
    yield connection  # Code after yield runs during cleanup
    connection.close()

What is a Patch in pytest?

Patching refers to temporarily replacing parts of the application, such as functions, classes, or methods, with mock objects during testing. This is particularly useful for isolating the code under test by mocking external dependencies or interactions.

In pytest, patching is typically done using the mock module from unittest or the pytest-mock plugin, which provides an easier way to patch objects.

Common Use Cases for Patching

  • Mocking APIs or database calls to avoid actual external requests.
  • Replacing functions that perform heavy computations.
  • Controlling the behavior of certain components during tests.

Example of Patching with pytest-mock

# Install pytest-mock: pip install pytest-mock

def get_user_from_db(user_id):
    # Simulate a database call
    return {"id": user_id, "name": "Danil"}

def greet_user(user_id):
    user = get_user_from_db(user_id)
    return f"Hello, {user['name']}!"

# Test with patching
def test_greet_user(mocker):
    # Mock the database call
    mock_db_call = mocker.patch("__main__.get_user_from_db", return_value={"id": 1, "name": "Mocked User"})
    
    # Test the function
    greeting = greet_user(1)
    assert greeting == "Hello, Mocked User!"
    mock_db_call.assert_called_once_with(1)

Example of Patching with unittest.mock

from unittest.mock import patch

# Test with patching using unittest.mock
def test_greet_user_with_patch():
    with patch("__main__.get_user_from_db", return_value={"id": 1, "name": "Mocked User"}):
        greeting = greet_user(1)
        assert greeting == "Hello, Mocked User!"

Differences Between Fixtures and Patch

Aspect Fixture Patch
Purpose Provides reusable setups and test data. Temporarily replaces objects or methods.
Scope Can have scoped lifetimes (function, module, etc.). Typically active only within a specific context.
Implementation Defined using @pytest.fixture. Achieved using mocker or unittest.mock.
Use Case Set up environments, databases, or test data. Mock dependencies or isolate the code under test.

Conclusion

Both fixtures and patching are essential tools in pytest, but they serve different purposes. Fixtures are ideal for setting up reusable test environments and data, while patching is used to isolate the code by mocking dependencies. Together, they provide a robust framework for writing modular and efficient tests.


Interview angle

  • “Fixture or patch?” - a fixture supplies a dependency the test explicitly requests; patching replaces something the code under test reaches for implicitly. Prefer injection and fixtures - if you need heavy patching, that’s usually a design signal.
  • “Where do you patch?” - where the name is looked up, not where it’s defined. Patch mymodule.requests.get, not requests.get, because the module already holds its own reference. This is the single most common mocking mistake.
  • Mock or MagicMock?” - MagicMock supports dunder protocols (iteration, context managers, comparisons); Mock doesn’t. Use autospec so the mock rejects calls that don’t match the real signature, which catches drift when the real API changes.