How to set up pytest-plone in your add-on#

This guide shows you how to add pytest-plone to an existing Plone add-on.

Before you start#

You need testing layers.

A testing layer is the object that builds a Plone site for your tests: it loads your ZCML, installs your GenericSetup profile, and hands the result to each test. By convention you declare yours in a testing.py module in your package, building on the fixtures plone.app.testing provides.

pytest-plone does not replace any of this. It takes the layers you already have and turns them into pytest fixtures. If your add-on has no testing.py yet, write one first—the packages that own the layer machinery document how:

See also

Install the package#

Add pytest-plone to your test dependencies:

[project.optional-dependencies]
test = [
    "pytest-plone",
    "plone.app.testing",
]

Write the conftest#

In your top-level conftest.py, import your testing layers and pass them to fixtures_factory together with a prefix for each. Inject the result into the module namespace so pytest discovers the fixtures.

from my.addon.testing import MY_ADDON_FUNCTIONAL_TESTING
from my.addon.testing import MY_ADDON_INTEGRATION_TESTING
from pytest_plone import fixtures_factory


pytest_plugins = ["pytest_plone"]


globals().update(
    fixtures_factory(
        (
            (MY_ADDON_FUNCTIONAL_TESTING, "functional"),
            (MY_ADDON_INTEGRATION_TESTING, "integration"),
        )
    )
)

The prefixes name the generated fixtures. With integration and functional you get integration, integration_class, integration_session, and the three functional counterparts. Stick to these two names unless you have a reason not to—the fixtures in Fixtures are built on them.

Write a test#

Ask for what you need by naming it.

def test_portal_title(portal):
    assert portal.title == "Plone site"

Run the suite#

pytest

Declare your package name#

Several add-on fixtures need to know which distribution is under test. Provide a package_name fixture in your conftest.py:

import pytest


@pytest.fixture
def package_name() -> str:
    return "my.addon"

This unlocks uninstalled, which removes the boilerplate from the canonical uninstall test.

Where to go next#