Skip to content

Testing (Pytest)

BetterPy's largest feature group — 49 features that supercharge your pytest workflow with fixture management, fixture inspections, parametrize helpers, mocking support, assertions, and test tree navigation.


Fixtures

BetterPy provides first-class, deep integration for pytest fixtures, refactorings, inspections, and documentation directly inside the IDE.

Pytest fixture engine

Since 2026.07.0 · Maturity Incubating

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-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.

Start with fixture-aware pytest support for a release-oriented overview of everything the shared fixture engine adds over plain PyCharm: pytest-style resolution, plugin fixtures, dynamic fixture strings, fixture typing, rename, Find Usages, and fixture-specific inspections.

Fixture Editor Integration

Enables deep integration of pytest fixtures into the IDE editor, making fixture names behave like normal Python symbols. BetterPy resolves fixture definitions with pytest-style precedence, then uses that model for navigation, completion, definition searches, hierarchy actions, renaming, and usages.

For the broader release overview of the shared fixture model, plugin support, dynamic fixture strings, typing, and inspections, see fixture-aware pytest support.

Fixture parameters resolve to the fixture definition that pytest would choose for that location.

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

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

# tests/api/test_users.py
def test_profile(user):
    assert user.name == "api"

In test_profile, Go to Declaration (⌘B / Ctrl+B) on user navigates to tests/api/conftest.py.

Typed parameter implementations

Go to Implementations (⌥⌘B / Ctrl+Alt+B) on a typed function parameter opens the concrete implementations of its declared class. This works for ordinary functions as well as pytest fixtures.

class Handler(ABC):
    @abstractmethod
    def handle(self): ...

class SyncHandler(Handler):
    def handle(self): ...

class AsyncHandler(Handler):
    def handle(self): ...

def dispatch(handler<caret>: Handler): ...

Invoking Go to Implementations on handler opens Choose Implementation of inferred type Handler and offers SyncHandler and AsyncHandler. The same inferred-type chooser is available on an injected fixture parameter when its fixture function declares that return type.

Completion

Fixture-name completion is available in test and fixture parameter lists. Suggestions include visible fixtures from the current file, imports, ancestor conftest.py files, and discovered plugin fixtures.

Cold completion returns available local or cached suggestions without waiting for complete discovery. Builtin, plugin, and parent-override fixtures may arrive in the cache later: continue typing or invoke completion again to see them. An untouched popup is not updated automatically, and background discovery never reopens a dismissed popup.

@pytest.fixture
def client(app):
    return app.test_client()

def test_health(cli<caret>):
    ...

Completion can suggest client and inserts it as a normal parameter. BetterPy skips names that are already controlled by @pytest.mark.parametrize, and it keeps self and cls out of fixture suggestions.

Rename And Usages

Renaming a fixture updates fixture parameters and known fixture references rather than only the Python function declaration. Find Usages (⌥F7 / Alt+F7) also treats fixture injections as usages, so a fixture can be audited before changing or deleting it.

@pytest.fixture
def api_client():
    return Client()

def test_status(api_client):
    assert api_client.get("/status").ok

Renaming api_client updates both the fixture definition and the injected parameter.

Override Hierarchy Support

Fixture definitions participate in IDE hierarchy workflows:

  • Go to Super from an overriding fixture opens the fixture it shadows in a broader scope.
  • Gutter markers on overridden fixtures list narrower-scope fixtures that override them.
  • Go to Type Declaration from a fixture parameter opens the fixture function that supplies the value.
  • Implement/Override Methods can offer fixture overrides when editing test classes that inherit fixture methods.

Fixture Injection Intentions

Since 2026.07.0 · Maturity Incubating · Issues PY-55931

Provides intentions to easily convert between explicit fixture parameters and @pytest.mark.usefixtures decorators.

Convert Between Parameter And Decorator Injection

Two intentions help switch between explicit fixture parameters and @pytest.mark.usefixtures.

def test_migrates_database(db):
    assert run_migration()
becomes:
@pytest.mark.usefixtures("db")
def test_migrates_database():
    assert run_migration()

@pytest.mark.usefixtures("db")
def test_migrates_database():
    assert run_migration()
becomes:
def test_migrates_database(db):
    assert run_migration()

Use the decorator form when a fixture is needed only for setup or teardown side effects. Use the parameter form when the test reads the fixture value.

Create pytest fixtures from parameters

Since 2026.07.0 · Maturity Incubating

Create pytest fixtures from parameters generates a fixture for an unresolved pytest fixture parameter.

How to use it

Place the caret on an unresolved fixture parameter in a test or fixture function, then run BetterPy: Create pytest fixture ....

When it helps

Use this quick fix when writing tests top-down and adding fixture parameters before defining the fixtures they refer to.

Notes

  • BetterPy creates a @pytest.fixture function in the appropriate scope and imports pytest when needed.
  • The feature is gated by the Create pytest fixtures from parameters setting under Settings | BetterPy | Testing (Pytest) | Fixtures.

Pull Up pytest fixture

Since 2026.09.4 · Maturity Incubating · Issues PY-63189

Place the caret in a fixture and choose Refactor | Pull Members Up.... A class fixture becomes module-scoped, while a fixture in a nested conftest.py moves to the parent conftest.py, widening the tests that can request it.

For example, class TestSuite: @pytest.fixture def db(self): ... becomes top-level @pytest.fixture def db(): ...; test methods keep requesting db. Required imports and referenced helpers move with the fixture, while conflicts leave both files unchanged.

A fixture in a test module can also move into conftest.py in the same directory. BetterPy creates the target conftest when needed and blocks the move if it would collide with an existing fixture name.

Push Down pytest fixture

Since 2026.09.4 · Maturity Incubating · Issues PY-56186

Place the caret in a fixture and choose Refactor | Push Members Down.... A module fixture can move into a test class, while a fixture in a parent conftest.py moves or copies into an immediate sibling pytest test module or a selected child conftest.py, narrowing where pytest exposes it.

For example, with tests/move/conftest.py defining tomove and the only consumer in tests/move/test_.py, BetterPy moves @pytest.fixture def tomove(): ... into test_.py and removes it from conftest.py. A fixture consumed by both api/ and ui/ can instead move or copy into selected child conftests. BetterPy keeps the source when consumers remain outside the selected exact file or recursive child-directory scope.

When consumers span several destinations and the fixture scope permits copying, BetterPy can populate all consumer destinations automatically. Otherwise, it offers a destination chooser. Sibling-module fixtures are visible only in that file; child-conftest fixtures are visible throughout that child directory. Conflicting destinations are excluded, and if none remain, no files are changed.

Pytest fixture documentation

Since 2026.05.2 · Maturity Stable · Issues PY-66245PY-57315PY-71347

Enhances Quick Documentation and member type inference for pytest fixtures across files.

Quick Documentation on a fixture or fixture parameter shows the resolved fixture type, dataclass/Pydantic/TypedDict fields, attribute docstrings, and literal values returned or yielded by simple fixtures. List, tuple, set, and dict literals are rendered across multiple lines so nested fixture data is easier to scan.

Supported fixture cases:

  • Primitive return types such as str, bool, list, dict, set, int, and float.
  • Generator-style fixtures annotated as Generator[T, ...], Iterator[T], Iterable[T], and their async variants; BetterPy documents and infers the yielded value type T.
  • Dataclasses, Pydantic models, and TypedDicts, including inherited fields, default values, and attribute docstrings.
  • Fixtures defined in conftest.py, including fixtures whose returned class is declared inside the fixture body.
  • The built-in request fixture, which is typed as _pytest.fixtures.SubRequest when available.

Parametrized test arguments are ignored because they shadow fixtures with the same name.

How to invoke: F1 / Ctrl+Q on a fixture parameter.

Example:

import pytest


@pytest.fixture
def api_config() -> dict:
    return {
        "base_url": "https://api.example.test",
        "headers": {"Accept": "application/json"},
        "timeouts": [1, 5, 10],
    }


def test_client(api_config):
    ...

Quick Documentation shows the fixture type and the returned dictionary value as a multiline block, including nested dictionaries and lists.

New pytest members

Since 2026.07.0 · Maturity Incubating

New pytest members adds Generate menu actions for creating pytest tests and fixtures in the current scope.

How to use it

Open a module or class matching the configured pytest collection naming, then run Generate | pytest test or Generate | pytest fixture.

When it helps

Use these actions when adding tests or fixtures near the caret without manually typing boilerplate or decorators.

Notes

  • New test names and fixture names are generated uniquely for the current file or class.
  • Generated tests and fixtures start as live templates so you can edit the name and body in place with Tab.
  • New tests start with assert False, "Not implemented" so they fail until you write the real assertion.
  • Fixture generation imports pytest when needed.
  • Test and fixture generation are hidden when neither the current module nor the containing class matches the configured pytest collection naming.
  • Test generation is hidden in conftest.py, while fixture generation remains available there.
  • The feature is gated by the New pytest members setting under Settings | BetterPy | Testing (Pytest) | Fixtures.

Pytest hooks

Since 2026.07.0 · Maturity Incubating · Issues PY-70762

BetterPy treats pytest hook functions as first-class symbols: it generates hook stubs where pytest actually discovers them, links implementations back to the hook specification, and shows the hook documentation inline.

Generate a hook implementation

Generate pytest hook inserts a hook implementation into the file pytest loads hooks from, such as the nearest conftest.py or a plugin module. The generated function is opened as a live template, so the parameter list and body can be adjusted immediately after insertion.

# conftest.py
def pytest_collection_modifyitems(session, config, items):
    ...
  • A gutter marker next to a hook implementation leads to the hook specification and to other implementations of the same hook.
  • Hook navigation discovers implementations in files matched by the active pytest python_files patterns as well as conftest.py and plugin modules.
  • Go to Declaration on a hook implementation in conftest.py resolves to the corresponding hook specification shipped with pytest.
  • Quick documentation renders the hook's documented contract, including its parameters and expected return value.
  • Hook names are real references in conftest.py, so renaming and Find Usages cover implementations across conftest.py files.

Notes

  • Hook generation follows pytest's own locations, including conftest.py ancestry, pytest_plugins declarations, and plugin modules. Navigation and reference-based rename/Find Usages currently apply to conftest.py implementations.
  • The feature can be toggled in Settings | Tools | BetterPy | Testing (Pytest) | Fixtures.

Fixture Inspections

Pytest fixture scope inspection

Since 2026.07.0 · Maturity Incubating · Issues PY-63194PY-61197

Pytest fixture scope inspection reports fixtures whose scope is narrower than a fixture they depend on.

How to use it

Open a pytest fixture that depends on another fixture with a broader scope. BetterPy highlights incompatible scope declarations.

When it helps

Use this inspection to catch scope mismatches before pytest fails collection or runtime fixture setup.

Notes

  • BetterPy can offer a quick fix for compatible scope adjustments.
  • The feature is gated by the Pytest fixture scope inspection setting under Settings | BetterPy | Testing (Pytest) | Fixture Inspections.

Pytest fixture to usefixtures inspection

Since 2026.08.3 · Maturity Incubating · Issues PY-55931

Pytest fixture to usefixtures inspection reports unused setup-only fixture parameters in tests and offers conversion to @pytest.mark.usefixtures(...).

How to use it

Open a test function that declares a setup-only fixture parameter and never references it in the test body. @pytest.mark.usefixtures(...) is the right replacement only when the fixture does not return or yield a value. Fixtures that provide a value stay as parameters so the unused result remains visible.

When it helps

Use this inspection to make setup-only fixture dependencies explicit without keeping unused parameters in the test signature.

Notes

  • BetterPy updates decorators and imports pytest when needed.
  • Pytest built-in fixtures such as capsys, tmp_path, monkeypatch, or pytester are never reported.
  • The feature is gated by the Pytest fixture to usefixtures inspection setting under Settings | BetterPy | Testing (Pytest) | Fixture Inspections.

Pytest fixture redundant parameter inspection

Since 2026.08.3 · Maturity Incubating

Pytest fixture redundant parameter inspection reports fixture parameters that are already injected by pytest mechanisms.

How to use it

Open a test that declares a fixture parameter also supplied through autouse, usefixtures, or module-level pytestmark.

When it helps

Use this inspection to remove fixture parameters that are not referenced by the function body and only repeat existing injection.

Notes

  • BetterPy avoids reporting parameters that are used in the function body.
  • The feature is gated by the Pytest fixture redundant parameter inspection setting under Settings | BetterPy | Testing (Pytest) | Fixture Inspections.

Unused pytest fixture inspection

Since 2026.07.0 · Maturity Incubating

Unused pytest fixture inspection reports fixture functions that are not used by tests or other fixtures.

How to use it

Open a file containing pytest fixtures. BetterPy highlights fixtures that have no detected references.

When it helps

Use this inspection to remove dead fixture code and keep test support modules easier to navigate.

Notes

  • BetterPy offers safe-delete for unused fixtures.
  • The feature is gated by the Unused pytest fixture inspection setting under Settings | BetterPy | Testing (Pytest) | Fixture Inspections.

Pytest fixture direct import inspection

Since 2026.07.0 · Maturity Incubating

Pytest fixture direct import inspection reports fixture functions imported directly into conftest.py.

How to use it

Open a conftest.py file that imports fixture functions directly. BetterPy highlights imports that should be replaced with explicit plugin scoping.

When it helps

Use this inspection to keep fixture sharing explicit and avoid surprising fixture availability across test packages.

Notes

  • Prefer pytest_plugins when exposing fixtures from another module.
  • The feature is gated by the Pytest fixture direct import inspection setting under Settings | BetterPy | Testing (Pytest) | Fixture Inspections.

Fixture annotation mismatch inspection

Since 2026.07.0 · Maturity Incubating

Reports test or fixture parameters whose explicit type annotation does not match the value type provided by the resolved fixture.

Pytest fixture override contract inspection

Since 2026.07.0 · Maturity Incubating

Reports pytest fixture overrides whose produced value type is incompatible with the parent fixture they shadow.

Pytest redundant fixture import inspection

Since 2026.07.0 · Maturity Incubating · Issues PY-63189

Reports explicitly imported pytest fixtures that are already auto-discoverable via conftest or plugins.

Examples

Redundant Import (Warning)
# conftest.py
import pytest

@pytest.fixture
def my_fixture():
    return 42

# Warning: Fixture 'my_fixture' is already provided by pytest (conftest/plugins); the explicit import is redundant.
from conftest import my_fixture

def test_example(my_fixture):
    assert my_fixture == 42
After Quick-Fix (Remove redundant fixture import)
# test_something.py

def test_example(my_fixture):
    assert my_fixture == 42

request.param without params inspection

Since 2026.07.0 · Maturity Incubating

Reports fixtures that read request.param without declaring params= and without being targeted by indirect parametrization.

Unknown dynamic pytest fixture inspection

Since 2026.07.0 · Maturity Incubating · Issues PY-54420

Reports unresolved string-based fixture requests in usefixtures, getfixturevalue, and indirect parametrization.

Pytest fixture uninjected reference inspection

Since 2026.07.0 · Maturity Incubating · Issues PY-71966PY-89003PY-43656

Pytest fixture uninjected reference inspection reports fixture names referenced in a test or fixture without being declared as parameters.

How to use it

Open a pytest function that uses a fixture name directly without injecting it.

When it helps

Use this inspection to catch accidental global-looking fixture references that pytest will not inject automatically.

Notes

  • BetterPy focuses on fixture functions and pytest test contexts.
  • The feature is gated by the Pytest fixture uninjected reference inspection setting under Settings | BetterPy | Testing (Pytest) | Fixture Inspections.

@pytest.mark.usefixtures on fixture is a no-op inspection

Since 2026.07.0 · Maturity Incubating · Issues PY-64332

@pytest.mark.usefixtures on fixture is a no-op inspection reports usefixtures decorators applied to fixture functions.

How to use it

Open a fixture function decorated with @pytest.mark.usefixtures(...). BetterPy highlights the decorator because pytest only applies it to tests.

When it helps

Use this inspection to find fixture setup assumptions that are silently ignored by pytest.

Notes

  • Move the dependency into the fixture parameter list instead.
  • The feature is gated by the @pytest.mark.usefixtures on fixture is a no-op inspection setting under Settings | BetterPy | Testing (Pytest) | Fixture Inspections.

pytest_plugins in non-root conftest inspection

Since 2026.07.0 · Maturity Incubating

pytest_plugins in non-root conftest inspection reports pytest_plugins assignments in non-root conftest.py files.

How to use it

Open a nested conftest.py file containing a pytest_plugins assignment. BetterPy highlights assignments that pytest will ignore.

When it helps

Use this inspection to move plugin declarations to the root conftest where pytest will honor them.

Notes

  • BetterPy uses pytest configuration discovery to distinguish root and nested conftest files.
  • The feature is gated by the pytest_plugins in non-root conftest inspection setting under Settings | BetterPy | Testing (Pytest) | Fixture Inspections.

Plugins

Pytest plugin references

Since 2026.08.3 · Maturity Incubating

Turns the module strings in a pytest_plugins assignment into real references, so conftest.py plugin wiring behaves like ordinary Python imports.

  • ⌘+Click / Ctrl+Click a plugin string to jump to the plugin module.
  • Renaming or moving a plugin module or its package updates the pytest_plugins strings automatically.
  • An inspection reports plugin strings that do not resolve to a module, at error level.
# conftest.py
pytest_plugins = [
    "plugins.database",   # navigates to plugins/database.py
    "plugins.typo",       # Cannot resolve pytest plugin module 'plugins.typo'
]

Single strings, list entries, tuple entries, and parenthesized strings are all supported. Project modules win over same-named library modules, and a package wins over a same-named module. Dynamically built values and f-strings are left alone: they neither get references nor are reported as unresolved.


Configuration

Missing pytest test directory

Since 2026.08.5 · Maturity Incubating

Highlights a configured pytest testpaths directory that does not exist and offers to create the missing directory.


Parametrize

Parametrize pytest test

Since 2026.07.0 · Maturity Incubating · Issues PY-31542PY-33483PY-48610PY-73660

Parametrize pytest test adds, removes, completes, and maintains pytest.mark.parametrize parameters for pytest tests.

How to use it

Place the caret in a pytest test or parametrize decorator, then run the available BetterPy parametrize intention. Inside a parametrized test's function parameter list, invoke basic completion and select the aggregate suggestion to insert every missing static argname in decorator order.

When it helps

Use this feature when turning a single test into a parametrized test, adding another parameter to an existing decorator, inserting all missing function parameters at once, removing an unused parameter, or renaming parametrized arguments safely.

Notes

  • The feature includes rename validation for parametrized argument names.
  • BetterPy keeps function parameters and decorator argnames synchronized.
  • Completion supports comma-separated strings, tuples of strings, lists of strings, and stacked decorators; dynamic argname expressions are left to normal completion.
  • The feature is gated by the Parametrize pytest test setting under Settings | BetterPy | Testing (Pytest) | Parametrize.

Pytest parametrized case run gutter icon

Since 2026.06.0 · Maturity Incubating · Issues PY-88558PY-46650PY-38868

This feature adds run gutter icons for individual pytest.mark.parametrize cases.

How to use it

Put each parametrized case on its own line, then use the gutter run icon beside the case to run only that pytest node. The regular run icon beside the test function remains available for running all of its cases, so you can switch between the whole test and an individual case.

import pytest

@pytest.mark.parametrize("value", [
    "admin",
    "guest",
])
def test_role(value):
    assert value
test_roles.py::test_role[guest]

Notes

  • The line marker appears only when BetterPy can infer the exact pytest case ID.
  • Explicit ids from ids=[...] and pytest.param(..., id="...") are supported.
  • IDs inferred from structured values such as tuples, lists, and dictionaries are normalized to the node IDs pytest uses.
  • The marker follows the configured python_files, python_classes, python_functions, and testpaths collection rules, including inherited test methods and explicit-path runs.
  • The feature is gated by the Pytest parametrized case run gutter icon setting under Settings | BetterPy | Testing (Pytest) | Parametrize.

Convert pytest.param

Since 2026.07.0 · Maturity Incubating

Convert pytest.param converts parametrized values between plain values and explicit pytest.param(...) calls.

How to use it

Place the caret inside a pytest.mark.parametrize values list, then run BetterPy: Convert argument to pytest.param() or the reverse conversion intention.

When it helps

Use this intention when adding or removing per-case metadata such as ids and marks while keeping the parameter value structure correct.

Notes

  • BetterPy imports pytest when converting plain values to pytest.param(...).
  • Values with marks or ids are not converted back to plain values.
  • The feature is gated by the Convert pytest.param setting under Settings | BetterPy | Testing (Pytest) | Parametrize.

Pytest parametrize count mismatch inspection

Since 2026.08.3 · Maturity Incubating · Issues PY-30323PY-49892

This inspection reports @pytest.mark.parametrize entries where the number of values does not match the number of argument names.

How to use it

Enable the inspection under Settings | BetterPy | Testing (Pytest) | Parametrize. BetterPy highlights mismatched parametrize rows in the editor.

Notes

  • The inspection understands plain value rows and pytest.param(...) rows.
  • It is gated by the Pytest parametrize count mismatch setting.

Pytest parametrize ids count inspection

Since 2026.08.3 · Maturity Incubating

This inspection reports ids=[...] lists whose length does not match the number of parametrized test cases.

How to use it

Enable the inspection under Settings | BetterPy | Testing (Pytest) | Parametrize. BetterPy highlights ids lists that do not line up with the parametrized values.

Notes

  • Matching ids counts keep pytest output names predictable.
  • The feature is gated by the Pytest parametrize ids count setting.

Pytest parametrize unused argname inspection

Since 2026.08.3 · Maturity Incubating

This inspection reports parametrized argument names that are not used by the target test body.

How to use it

Enable the inspection under Settings | BetterPy | Testing (Pytest) | Parametrize. BetterPy highlights unused argnames and the corresponding values.

Notes

  • The inspection accounts for stacked parametrize decorators.
  • It is gated by the Pytest parametrize unused argname setting.

Pytest parametrize unexpected keyword inspection

Since 2026.08.3 · Maturity Incubating

This inspection reports unsupported keyword arguments passed to pytest.mark.parametrize.

How to use it

Enable the inspection under Settings | BetterPy | Testing (Pytest) | Parametrize. BetterPy highlights misspelled or unsupported keyword names such as testids.

Notes

  • Supported keyword arguments are argnames, argvalues, indirect, ids, scope, and pytest's internal _param_mark argument.
  • The feature is gated by the Pytest parametrize unexpected keyword setting.

Assertions & Test Editing

Use actual test outcome

Since 2026.06.0 · Maturity Incubating

Replaces assertion expected values with the actual value from the last failed test run.

Failed tests are matched with the python_files, python_classes, and python_functions rules from the pytest configuration that applies to their project root, so custom test names and multi-root projects are supported.

How to invoke: ⌥⏎ / Alt+Enter on a failed assertion → "BetterPy: Use actual test outcome"

def test_total():
    assert calculate_total([10, 20]) == 25  # Failed: got 30
def test_total():
    assert calculate_total([10, 20]) == 30

Toggle pytest skip

Since 2026.07.0 · Maturity Incubating · Issues PY-56066

Toggle pytest skip adds or removes pytest skip markers for tests, test classes, modules, and parameter values.

How to use it

Place the caret on a supported pytest target, then run BetterPy: Toggle pytest skip from the intention menu.

When it helps

Use this intention when triaging tests and you need to skip or unskip the current target without editing decorators manually.

Notes

  • The intention supports functions, classes, module-level pytestmark, pytest.param(...), and simple parameter values in parametrized tests.
  • The feature is gated by the Toggle pytest skip setting under Settings | BetterPy | Testing (Pytest) | Assertions & Test Editing.

Surround with pytest.raises()

Since 2026.07.0 · Maturity Incubating

Surround with pytest.raises() wraps selected test statements in a with pytest.raises(Exception): block.

How to use it

Select one or more statements in a pytest test, then use the Python surround action and choose pytest.raises().

When it helps

Use this surrounder when converting a failing statement into an explicit exception assertion while keeping indentation and imports handled for you.

Notes

  • BetterPy adds a pytest import when needed.
  • The surrounder is available only in pytest test functions and avoids wrapping code already inside pytest.raises.
  • The feature is gated by the Surround with pytest.raises() setting under Settings | BetterPy | Testing (Pytest) | Assertions & Test Editing.

Pytest failed line inspection

Since 2026.06.0 · Maturity Incubating · Issues PY-32337PY-61067

Marks the line where a pytest test failed, providing visual feedback directly in the editor.

def test_total():
    total = calculate_total([10, 20])
    assert total == 25
def test_total():
    total = calculate_total([10, 20])
    assert total == 25  # highlighted when pytest reports this line as failed

Notes

  • The highlight is based on the last pytest failure location remembered by BetterPy.
  • The feature works together with Use actual test outcome when an assertion diff is available.
  • The feature is gated by the Pytest failed line inspection setting under Settings | BetterPy | Testing (Pytest) | Assertions & Test Editing.

Pytest missing assert inspection

Since 2026.08.3 · Maturity Incubating · Issues PY-57064

This inspection reports pytest tests that never assert anything, and tests that explicitly return a value instead of asserting. Both cases silently pass in pytest, so the test looks green while it verifies nothing.

How to use it

Enable the inspection under Settings | BetterPy | Testing (Pytest) | Code Inspections. BetterPy highlights the test name of a test without assertions, and the return <value> statement of a test that returns a value.

def test_user_is_created():
    user = create_user("Alice")
    print(user.name)  # Pytest test has no assert statement

def test_user_name():
    user = create_user("Alice")
    assert user.name == "Alice"
    return user  # Pytest test should not return a value
def test_user_is_created():
    user = create_user("Alice")
    assert user is not None

def test_user_name():
    user = create_user("Alice")
    assert user.name == "Alice"

What counts as an assertion

BetterPy does not just look for a literal assert in the test body. A test is considered to assert something when it:

  • contains a Python assert statement,
  • raises AssertionError explicitly, for example raise AssertionError("unexpected value"),
  • uses pytest.raises(...), pytest.warns(...), or pytest.deprecated_call(), whether as a context manager or as a direct call such as the legacy pytest.raises(ValueError, func, *args) form,
  • calls pytest.fail(...), pytest.xfail(...), or pytest.skip(...) as the terminating check,
  • calls a unittest.TestCase-style assertion on self or cls, such as self.assertEqual(...), self.assertTrue(...), or self.assertRaises(...),
  • calls a fluent assertion helper such as assert_that(...) from assertpy or PyHamcrest,
  • calls a mock assertion method such as assert_called_once_with(...) or assert_not_awaited() on a value BetterPy recognises as a Mock or AsyncMock,
  • or calls a project helper function that itself asserts. Helper chains are followed across files in your own code, up to a bounded depth, and recursive helpers are handled without hanging.

All pytest forms are recognised through the same alias-aware resolution, so import pytest, import pytest as pt, from pytest import warns, and from pytest import raises as expect_error all work.

What is deliberately not reported

  • Functions that pytest would not collect: test-named functions in non-test files, methods in classes that are not test classes, and helper functions or fixtures that happen to start with test_. BetterPy honours python_files, python_classes, python_functions, and testpaths from your pytest configuration.
  • A bare return without a value, and return statements inside nested functions.
  • Assertions inside a nested function that is never called — those do not satisfy the inspection, so such a test is still reported.
  • assert_-prefixed methods on ordinary objects, on Mock(spec=...) where the spec class defines the method, or on Mock(unsafe=True); only real mock assertion methods count. unittest-style assertions are only accepted in their camelCase form (assertEqual, not assert_equal) and only when called on self or cls.
  • Calls that cannot be resolved, for example helpers from third-party libraries — the test is reported, because BetterPy cannot see an assertion.

Notes

  • The inspection reports at weak warning level and only runs on your own code, not on library sources.
  • It is gated by the Pytest missing assert inspection setting.

Pytest mark completion

Since 2026.08.3 · Maturity Incubating · Issues PY-57354

Pytest mark completion suggests known pytest markers in pytest.mark contexts.

How to use it

Invoke completion after pytest.mark. in a test file.

import pytest

@pytest.mark.<caret>
def test_example():
    ...

Where the markers come from

  • Pytest's built-in markers, such as skip, skipif, xfail, parametrize, and usefixtures.
  • Markers registered in your project configuration under markers = ... in pyproject.toml, pytest.ini, or setup.cfg.
  • Markers contributed by installed pytest plugins, taken from BetterPy's pytest collection snapshot.

Built-in and configured markers are suggested immediately, without waiting for a pytest collection run. Each lookup entry shows the marker description and, for plugin markers, the contributing plugin.

The same marker knowledge also drives go-to-declaration and rename for a marker name, a gutter icon on marker declarations, quick documentation for marker usages, and the Unknown pytest marker in strict mode inspection.

Notes

  • Completion is offered only in real marker positions. import pytest, import pytest as pt, and from pytest import mark are recognised; unrelated decorators and a local object named pytest are ignored.
  • The feature is gated by the Pytest mark completion setting.

Test Tree & Navigation

Pytest collection-pattern IDE integration

Since 2026.08.5 · Maturity Stable

Collection-aware run actions

BetterPy keeps editor run gutters and Run/Debug context actions aligned with pytest's configured python_files, python_classes, python_functions, and testpaths. It adds run affordances for custom-named tests that pytest will collect and hides them where the configured rules exclude a file, class, or function, including inherited tests and explicit-path runs.

When renaming a collected test file, the rename dialog preselects the editable stem after the configured python_files prefix. For example, test_login.py selects login; a project configured with python_files = check_*.py selects login in check_login.py.

Copy pytest node IDs

Since 2026.07.0 · Maturity Incubating · Issues PY-57904

Copy pytest node IDs copies pytest node identifiers for selected tests to the clipboard.

How to use it

Select one or more tests in the pytest test tree or pytest explorer, then run Copy Special | BetterPy: Copy Pytest Node IDs.

When it helps

Use this action when rerunning a specific test selection from a terminal, sharing an exact pytest target, or preparing command-line invocations.

Notes

  • Multiple selected node IDs are copied one per line.
  • The feature is gated by the Copy pytest node IDs setting under Settings | BetterPy | Testing (Pytest) | Test Tree & Navigation.

Copy pytest node ID from editor

Since 2026.07.0 · Maturity Incubating · Issues PY-49998

Copy pytest node ID from editor copies the pytest node ID for the test at the caret.

How to use it

Place the caret inside a pytest test function, then run BetterPy: Copy Pytest Node ID from Copy Special.

When it helps

Use this action when you want to rerun the current test from a terminal or share an exact pytest target without opening the test tree.

Notes

  • The action is available only inside pytest test functions.
  • The feature is gated by the Copy pytest node ID from editor setting under Settings | BetterPy | Testing (Pytest) | Test Tree & Navigation.

Jump to pytest node in test tree

Since 2026.07.0 · Maturity Incubating

Jump to pytest node in test tree selects the matching node in the active pytest test tree for the test at the editor caret.

How to use it

Place the caret inside a pytest test function or class, then run BetterPy: Jump to Test Tree Node from the editor context menu or intention list.

The same entry is also offered in the run gutter menu of the test: click the run icon next to the test function or class and pick BetterPy: Jump to Test Tree Node.

When it helps

Use this feature after a pytest run when the editor is focused but you want to inspect the matching result node, failure state, or surrounding suite in the test tree.

Notes

  • The action is available only when there is an active non-empty test tree.
  • The feature no longer adds a separate gutter icon; it contributes an item to the existing run gutter menu.
  • The feature is gated by the Jump to pytest node in test tree setting under Settings | BetterPy | Testing (Pytest) | Test Tree & Navigation.

Toggle pytest skip from test tree

Since 2026.07.0 · Maturity Incubating

Toggle pytest skip from test tree adds or removes @pytest.mark.skip for selected pytest nodes.

How to use it

Select a pytest function, class, module, or pytest explorer entry, then run BetterPy: Toggle Pytest Skip from the context menu.

When it helps

Use this action when triaging failures from the test results view and you want to skip or unskip the affected test without navigating manually through the project tree.

Notes

  • Function selections toggle the marker on that function.
  • Class and module selections toggle the marker at the matching scope.
  • The feature is gated by the Toggle pytest skip from test tree setting under Settings | BetterPy | Testing (Pytest) | Test Tree & Navigation.

Run pytest with debug logging

Since 2026.07.0 · Maturity Incubating

Run pytest with debug logging duplicates the selected pytest run configuration, adds DEBUG CLI logging, and runs the selected pytest node.

How to use it

Select a pytest node in the test tree, then run BetterPy: Run Pytest with DEBUG logging from the test tree context menu.

When it helps

Use this action when a failing test needs extra pytest log output but you do not want to permanently change the original run configuration.

Notes

  • The duplicated configuration is temporary.
  • The duplicated configuration is narrowed to the selected test tree node (the pytest node id is targeted directly), so only that node runs.
  • BetterPy appends --log-cli-level=DEBUG when it is not already present.
  • The feature is gated by the Run pytest with debug logging setting under Settings | BetterPy | Testing (Pytest) | Test Tree & Navigation.

Pytest identifier in Search Everywhere

Since 2026.07.0 · Maturity Incubating · Issues PY-57148

This Search Everywhere contributor finds pytest node identifiers directly from the IDE search popup.

How to use it

Open Search Everywhere and type a pytest node id or identifier fragment.

tests/test_api.py::TestUser::test_create

Notes

  • Results navigate to the matching Python PSI element.
  • The feature is gated by the Pytest identifier Search Everywhere setting.

Mocking

Mock assertion methods

Since 2026.08.0 · Maturity Stable

Resolves standard Mock and AsyncMock assertion helpers such as assert_called_once_with without enabling broader mock type inference.

Mock patch reference

Since 2026.08.0 · Maturity Stable

Provides reference resolution for mock.patch target strings, enabling ⌘+Click / Ctrl+Click navigation to the patched symbol.

Mock patch object attribute completion

Since 2026.08.0 · Maturity Stable

Completes attributes for mock.patch.object targets.

with mock.patch.object(UserService, "|"):  # Caret here
    pass
with mock.patch.object(UserService, "get_user"):
    pass

Mock type provider

Since 2026.08.0 · Maturity Stable

Provides type information for unittest.mock objects, improving code completion and type checking for mocked values.

Mock unresolved reference inspection

Since 2026.08.0 · Maturity Stable

Reports unresolved references in mock usage