Skip to main content

Give you the ability to write typed fixture classes that work well with dependency injection, autocompletetion, type checkers, and language servers

Project description

Pytest Fixture Classes

Give you the ability to write typed fixture classes that work well with dependency injection, autocompletetion, type checkers, and language servers.

No mypy plugins required!

Warning

This library is work in progress until version 1.0.0 so problems could occur on python versions less than 3.10. However, all problems will be simple exceptions happening at pytest startup so they won't secretly mess any of your tests up.

Installation

pip install pytest-fixture-classes

Usage

Quickstart

This is a quick and simple example of writing a very simplistic fixture class. You can, of course, add any methods you like into the class but I prefer to keep it a simple callable.

from pytest_fixture_classes import fixture_class
from collections.abc import Mapping
import requests


# changing the name is optional and is a question of style. Everything will work correctly with the default name
@fixture_class(name="my_fixture_class")
class MyFixtureClass:
    existing_fixture1: Mapping[str, str]
    existing_fixture2: requests.Session
    existing_fixture3: Mapping[str, str | int | bool]

    def __call__(self, name: str, age: int, height: int) -> dict[str, str | int | bool]:
        ...


def test_my_code(my_fixture_class: MyFixtureClass):
    some_value = my_fixture_class(...)
    some_other_value = my_fixture_class(...)
    one_more_value = my_fixture_class(...)

    # Some testing code below
    ...

Rationale

If we want factory fixtures that automatically make use of pytest's dependency injection, we are essentially giving up any IDE/typechecker/language server support because such fixtures cannot be properly typehinted because they are returning a callable, not a value. And python is still pretty new to typehinting callables.

So we can't use ctrl + click, we don't get any autocompletion, and mypy/pyright won't warn us when we are using the factory incorrectly. Additionally, any changes to the factory's interface will require us to search for its usages by hand and fix every single one.

Fixture classes solve all of the problems I mentioned:

  • Autocompletion out of the box
  • Return type of the fixture will automatically be inferred by pyright/mypy
  • When the interface of the fixture changes or when you use it incorrectly, your type checker will warn you
  • Search all references and show definition (ctrl + click) also works out of the box

Usage scenario

Let's say that we have a few pre-existing fixtures: db_connection, http_session, and current_user. Now we would like to write a new fixture that can create arbitrary users based on name, age, and height arguments. We want our new fixture, create_user, to automatically get our old fixtures using dependency injection. Let's see what such a fixture will look like:

import pytest
import requests

@pytest.fixture
def db_connection() -> dict[str, str]:
    ...

@pytest.fixture
def http_session() -> requests.Session:
    ...


@pytest.fixture
def current_user() -> requests.Session:
    ...


@pytest.fixture
async def create_user(
    db_connection: dict[str, str],
    http_session: requests.Session,
    current_user: requests.Session,
) -> Callable[[str, int, int], dict[str, str | int | bool]]:
    async def inner(name: str, age: int, height: int):
        user = {...}
        self.db_connection.execute(...)
        if self.current_user[...] is not None:
            self.http_session.post(...)
        
        return user

    return inner

def test_my_code(create_user: Callable[[str, int str], dict[str, str | int | bool]]):
    johny = create_user("Johny", 27, 183)
    michael = create_user("Michael", 43, 165)
    loretta = create_user("Loretta", 31, 172)

    # Some testing code below
    ...

See how ugly and vague the typehints for create_user are? Also, see how we duplicate the return type and argument information? Additionally, if you had thousands of tests and if test_my_code with create_user were in different files, you would have to use plaintext search to find the definition of the fixture if you wanted to see how to use it. Not too nice.

Now let's rewrite this code to solve all of the problems I mentioned:

from pytest_fixture_classes import fixture_class
from collections.abc import Mapping
import requests
import pytest


@pytest.fixture
def db_connection() -> dict[str, str]:
    ...


@pytest.fixture
def http_session() -> requests.Session:
    ...


@pytest.fixture
def current_user() -> Mapping[str, str | int | bool]:
    ...


@fixture_class(name="create_user")
class CreateUser:
    db_connection: Mapping[str, str]
    http_session: requests.Session
    current_user: Mapping[str, str | int | bool]

    def __call__(self, name: str, age: int, height: int) -> dict[str, str | int | bool]:
        user = {...}
        self.db_connection.execute(...)
        if self.current_user[...] is not None:
            self.http_session.post(...)
        
        return user


def test_my_code(create_user: CreateUser):
    johny = create_user("Johny", 27, 183)
    michael = create_user("Michael", 43, 165)
    loretta = create_user("Loretta", 31, 172)

    # Some testing code below
    ...

Implementation details

  • The fixture_class decorator turns your class into a frozen dataclass with slots so you won't be able to add new attributes to it after definiton. You can, however, define any methods you like except __init__.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytest-fixture-classes-0.1.1.tar.gz (5.5 kB view details)

Uploaded Source

Built Distribution

pytest_fixture_classes-0.1.1-py3-none-any.whl (5.5 kB view details)

Uploaded Python 3

File details

Details for the file pytest-fixture-classes-0.1.1.tar.gz.

File metadata

  • Download URL: pytest-fixture-classes-0.1.1.tar.gz
  • Upload date:
  • Size: 5.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.15 CPython/3.10.8 Linux/5.19.17-1-MANJARO

File hashes

Hashes for pytest-fixture-classes-0.1.1.tar.gz
Algorithm Hash digest
SHA256 8878c21b3a5c8118b943056f7177cf5e0b8751a8d98e1e4cab3ffb520f7dfc48
MD5 d0003af1d92ffd77fa2f3d0fae6f3d59
BLAKE2b-256 d62739ce63c854c6bd1ceceaa2084f998dd26c653cbff2fc60d037f92ef1e617

See more details on using hashes here.

File details

Details for the file pytest_fixture_classes-0.1.1-py3-none-any.whl.

File metadata

File hashes

Hashes for pytest_fixture_classes-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 dac75b849a5d39fb75e1411a56b4297bcec69656ffb9513deecb24c3543f6cfe
MD5 43630e4c4a65de8a34975f952e701388
BLAKE2b-256 da97a210bccc46a7654bcf969400b8ba621c338e79e19d1d2e259444ba7f3d35

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page