writing_plugins.rst

Overview

This documentation file provides comprehensive guidance on writing, discovering, and managing plugins in the pytest testing framework. Plugins extend pytest's core functionality by implementing hook functions and fixtures for configuration, test collection, execution, reporting, and more.

The file covers:

This file is primarily a conceptual and procedural guide rather than a source code file, but it includes essential code snippets and configuration examples.


Plugin Discovery and Loading Order

pytest loads plugins at startup in a defined sequence:

  1. Blocking plugins specified with -p no:name on the command line.

  2. Loading all builtin plugins internal to pytest.

  3. Loading plugins explicitly listed with -p name.

  4. Loading third-party plugins registered via setuptools entry points unless disabled by PYTEST_DISABLE_PLUGIN_AUTOLOAD.

  5. Loading plugins specified by the environment variable PYTEST_PLUGINS.

  6. Loading all initial conftest.py files in test directories recursively (including loading plugins defined in their pytest_plugins variables).

This order ensures precise control and predictability over plugin loading.


Conftest.py: Local Per-directory Plugins

Local plugins are implemented in `conftest.py` files within test directories. They provide directory-specific hook implementations. Hooks in `conftest.py` files apply only to tests in or below that directory.

Example:

# a/conftest.py
def pytest_runtest_setup(item):
    print("setting up", item)

**Notes:**


Writing Your Own Plugin

Plugins implement hooks and/or fixtures to extend pytest. Examples include builtin plugins and external ones like the YAML plugin.

**Helpful resources:**

Contributing your plugin to pytest-dev is encouraged once it has users.


Making Your Plugin Installable by Others

To distribute a plugin, define an entry point in your packaging metadata under the `pytest11` group.

Example `pyproject.toml` snippet:

[project.entry-points.pytest11]
myproject = "myproject.pluginmodule"

pytest will load `myproject.pluginmodule` as a plugin.

**Important:**


Assertion Rewriting

pytest rewrites Python assert statements in test modules and plugins to provide detailed failure introspection.

Example to register assertion rewriting for a helper module:

# pytest_foo/__init__.py
import pytest
pytest.register_assert_rewrite("pytest_foo.helper")

Requiring/Loading Plugins in Tests or Conftests

Use the global variable `pytest_plugins` to require plugins within test modules or `conftest.py` files:

pytest_plugins = ["name1", "name2"]

Plugins loaded via `pytest_plugins` are automatically registered for assertion rewriting unless already imported.


Accessing Another Plugin by Name

Plugins can collaborate by referencing one another through the plugin manager:

plugin = config.pluginmanager.get_plugin("name_of_plugin")

Use `pytest --trace-config` to view loaded plugins and their names.


Registering Custom Markers

Plugins that define custom markers should register them to avoid warnings and improve usability.

Example:

def pytest_configure(config):
    config.addinivalue_line("markers", "cool_marker: this one is for cool tests.")
    config.addinivalue_line("markers", "mark_with(arg, arg2): this marker takes arguments.")

Testing Plugins with the pytester Plugin

pytest includes a plugin called `pytester` that helps write tests for plugins.

Example plugin fixture:

import pytest

def pytest_addoption(parser):
    group = parser.getgroup("helloworld")
    group.addoption(
        "--name",
        action="store",
        dest="name",
        default="World",
        help='Default "name" for hello().',
    )

@pytest.fixture
def hello(request):
    def _hello(name=None):
        if not name:
            name = request.config.getoption("name")
        return f"Hello {name}!"
    return _hello

Testing the plugin with `pytester`:

def test_hello(pytester):
    pytester.makeconftest(
        """
        import pytest

        @pytest.fixture(params=["Brianna", "Andreas", "Floris"])
        def name(request):
            return request.param
        """
    )
    pytester.makepyfile(
        """
        def test_hello_default(hello):
            assert hello() == "Hello World!"

        def test_hello_name(hello, name):
            assert hello(name) == f"Hello {name}!"
        """
    )
    result = pytester.runpytest()
    result.assert_outcomes(passed=4)

`pytester` supports copying example files and running tests in isolated environments.


Visual Diagram: Plugin Workflow and Structure

flowchart TD
    A[pytest Startup] --> B{Plugin Discovery Order}
    B -->|1. Blocked Plugins (-p no:name)| C[Skip Plugins]
    B -->|2. Load Builtin Plugins| D[Builtin Plugins]
    B -->|3. Load Plugins (-p name)| E[Explicit Plugins]
    B -->|4. Load Entry Point Plugins| F[External Plugins]
    B -->|5. Load Env Vars (PYTEST_PLUGINS)| G[Env Plugins]
    B -->|6. Load conftest.py Files| H[Local Plugins (conftest.py)]

    subgraph Plugin Types
        D
        E
        F
        G
        H
    end

    Plugin Types --> I[Hook Function Calls]
    I --> J[Test Collection, Setup, Run, Reporting]
    J --> K[Assertion Rewriting]
    J --> L[Fixtures and Markers]
    J --> M[Testing Plugins (pytester)]

    style A fill:#f9f,stroke:#333,stroke-width:2px
    style B fill:#bbf,stroke:#333,stroke-width:2px

Summary

This documentation provides both conceptual explanations and practical examples to help developers write, distribute, and test pytest plugins effectively.