Skip to content

Fixture-aware pytest support

BetterPy makes pytest fixtures behave like first-class symbols in PyCharm. Plain PyCharm can navigate and complete many Python names, but fixture names are not ordinary imports or local variables: pytest resolves them through test location, class context, conftest.py ancestry, overrides, plugin loading, usefixtures, getfixturevalue, indirect parametrization, and built-in fixtures.

The Pytest fixture engine is the shared model behind BetterPy's fixture navigation, completion, type inference, Find Usages, rename support, and fixture inspections. It is enabled by default and can be toggled in Settings -> Tools -> BetterPy -> Testing (Pytest) -> Fixtures.

What BetterPy adds over plain PyCharm

BetterPy resolves fixture names with pytest's effective visibility rules instead of treating every same-named parameter as a plain Python parameter.

  • A fixture parameter goes to the fixture pytest would actually inject at that location.
  • Fixture completion includes visible fixtures from the current module, classes, ancestor conftest.py files, pytest plugin modules, installed pytest plugins, and common pytest built-ins.
  • Rename and Find Usages include injected parameters and string-based fixture requests instead of stopping at the fixture function declaration.
  • Type inference for fixture parameters uses the resolved fixture return or yield value.
  • Fixture inspections reuse the same resolution model, so warnings account for overrides, autouse fixtures, plugins, and dynamic fixture requests.

Resolution that follows pytest

BetterPy understands the fixture shapes that appear in real test suites:

  • Same-module fixtures, class fixtures, nested class fixtures, and fixtures from inherited test classes.
  • Nearest-scope wins between class, module, local conftest.py, and parent conftest.py declarations.
  • @pytest.fixture(name="...") and assignment-style fixtures such as fixture_name = pytest.fixture()(_factory).
  • Top-level imperative registrations such as pytest.fixture(name="fixture_name")(_factory).
  • Fixture dependencies that intentionally request an outer fixture with the same name while overriding it.
  • autouse=True, declared fixture scope=, and static or dynamic params=.
  • uv workspace member boundaries, so a conftest.py from another workspace member does not leak into the current member.
  • Common pytest built-ins including request, tmp_path, tmpdir, monkeypatch, capsys, capfd, caplog, recwarn, pytestconfig, and cache.

Example:

# tests/conftest.py
import pytest


@pytest.fixture
def user():
    return User(role="default")


# tests/admin/conftest.py
@pytest.fixture
def user():
    return User(role="admin")


# tests/admin/test_permissions.py
def test_permissions(user):
    assert user.role == "admin"

In tests/admin/test_permissions.py, Go to Declaration on user opens the admin fixture, not the parent fixture.

Dynamic fixture strings

Fixture names in strings become real references when pytest treats the string as a fixture request:

  • @pytest.mark.usefixtures("db")
  • module or class pytestmark = pytest.mark.usefixtures("db")
  • request.getfixturevalue("db")
  • @pytest.mark.parametrize("db", [...], indirect=True)
  • @pytest.mark.parametrize(("db", "case"), [...], indirect=["db"])

That means navigation, completion, rename, and unknown-fixture warnings work in string literals too. Direct parametrization stays separate: a plain @pytest.mark.parametrize("case", [...]) argument is treated as a test parameter, not as a fixture.

Example:

@pytest.fixture
def seeded_user():
    return UserFactory.create()


@pytest.mark.usefixtures("seeded_user")
def test_profile_page():
    ...

Renaming seeded_user updates the fixture declaration and the usefixtures string.

Plugin fixtures

BetterPy includes fixtures loaded through pytest plugin mechanisms, so plugin fixtures show up in the same editor workflows as project fixtures.

  • pytest_plugins = ["tests.plugins.auth"] in a root conftest.py.
  • Installed pytest plugins exposed through pytest11 entry points.
  • Entry points declared in pyproject.toml or setup.cfg.
  • Fixtures imported or re-exported from plugin modules.
  • Assignment-style plugin fixtures where the injected name differs from the helper function name.

Project fixtures still win over plugin fixtures with the same name, matching pytest's closest-wins behavior. BetterPy also highlights plugin-provided fixture usage so it is easier to see when a parameter comes from a plugin rather than from local test code.

Editor workflows

The engine powers the normal PyCharm actions you already use:

Fixture Editor Integration metadata links: PY-87756, PY-81495, PY-60973, PY-56268, PY-57812, PY-63269, PY-61634, PY-89790, PY-49850, PY-59721, PY-53176, PY-61040, PY-88555, PY-60421.

  • Go to Declaration from fixture parameters and dynamic fixture strings.
  • Go to Type Declaration from a fixture parameter or usage to the fixture that supplies the value.
  • Go to Super from an overriding fixture to the parent fixture it shadows.
  • Show Implementations from a parent fixture to narrower fixtures that override it.
  • Gutter markers for fixture override relationships.
  • Completion in test and fixture parameter lists, usefixtures, getfixturevalue, and indirect parametrization.
  • Rename from fixture declarations, injected parameters, and supported dynamic strings.
  • Find Usages that reports effective fixture consumers and respects search scope, fixture overrides, plugin fixtures, and dynamic string requests.
  • Fixture refactorings that reuse this model include Inline pytest fixture (PY-66243) and Push Down pytest fixture (PY-56186).

BetterPy also keeps false positives down. It ignores self, cls, lambda parameters, directly parametrized test arguments, parameters injected by mock.patch, and functions whose non-pytest decorators make fixture injection ambiguous.

Type inference and documentation

Fixture parameter types come from the resolved fixture, not from the parameter name.

  • Return fixtures type the injected parameter as the returned value.
  • Yield fixtures annotated as Iterator[T], Iterable[T], Generator[T, ...], and async variants type the injected parameter as T.
  • Async fixtures unwrap awaited return values.
  • Built-in fixtures carry known types where BetterPy has a safe catalog entry.
  • Shared fixtures can narrow dependency types by consumer scope when local overrides change what pytest injects.
  • Quick Documentation can show fixture types, returned literals, dataclass fields, Pydantic fields, TypedDict fields, and attribute docstrings. Related YouTrack issue: PY-66245.

Example:

@pytest.fixture
def api_token() -> str:
    return "token"


def test_client(api_token):
    reveal_type(api_token)  # str

See also: Pytest fixture documentation.

Fixture inspections

Several BetterPy inspections are only useful because they share the same fixture model:

  • Unknown fixture parameters and unknown dynamic fixture strings.
  • Uninjected fixture references inside tests or fixtures. Related YouTrack issues: PY-71966, PY-89003.
  • Unused fixtures, including fixtures used through usefixtures, getfixturevalue, indirect parametrization, autouse, plugin loading, or another fixture.
  • Scope mismatches where a broader-scoped fixture depends on a narrower-scoped fixture.
  • Fixture annotation mismatches where a test or fixture parameter annotation disagrees with the resolved fixture value type.
  • Fixture override contract mismatches where an overriding fixture produces an incompatible value type.
  • request.param reads in fixtures that have no params= and are not reached by indirect parametrization.
  • pytest_plugins assignments in non-root conftest.py files, which pytest ignores.
  • PyCharm inspection suppression for valid pytest patterns such as fixture method overrides and parametrized classes.

Related pages:

Performance model

The engine is index-backed and cache-aware. BetterPy indexes fixture declarations, uses word-search-backed consumer lookup for usages and inspections, limits conftest.py fixture searches to the subtree where pytest can see them, and caches pytest root/config and plugin fixture discovery.

This matters most in large test suites: opening a conftest.py, running inspections, or finding usages of a shared fixture should not require parsing every test file in the project.