Skip to content
This repository was archived by the owner on Apr 8, 2025. It is now read-only.

mrzechonek/pytest-paramark

Repository files navigation

pytest-paramark

PyPI version Python versions See Build Status on Travis CI

Configure pytest fixtures using combination of parametrize and markers


What is this thing?

The problem

Pytest fixture names must be unique within the whole dependency graph (#3966).

This means that when you want to parametrize fixtures, each argument name must be unique, and you have to remember which fixture uses which argument (or introduce some kind of convention):

import pytest

@pytest.fixture
def foo(foo_option):
    return {'option': foo_option}


@pytest.fixture
def bar(bar_option):
    return {'option': bar_option}


@pytest.mark.parametrize(
    'foo_option, bar_option',
    [
        (42, 24),
    ]
)
def test_options(foo, bar):
    assert foo['option'] == 42
    assert bar['option'] == 24

Also, if you want to provide default vaules for options, they need to be fixtures as well:

@pytest.fixture
def foo_option():
    return 'default_foo_option'


@pytest.fixture
def bar_option():
    return 'default_bar_option'


@pytest.fixture
def foo(foo_option):
    return {'option': foo_option}


@pytest.fixture
def bar(bar_option):
    return {'option': bar_option}


def test_options(foo, bar):
    assert foo['option'] == 'default_foo_option'
    assert bar['option'] == 'default_bar_option'

In some cases, indirect parametrization helps, but the usage is rather verbose, requires adding indirect specifiers to all tests, and is a bit inconvenient when you want to specify defaults:

@pytest.fixture
def foo(request):
    default = {'option': 'default_foo_option'}
    return {**defaults, **getattr(request, 'param', {})}


@pytest.fixture
def bar(request):
    default = {'option': 'default_bar_option'}
    return {**defaults, **getattr(request, 'param', {})}


@pytest.mark.parametrize('foo', [dict(option='custom_foo_option')], indirect=['foo'])
def test_options(foo, bar):
    assert foo['option'] == 'custom_foo_option'
    assert bar['option'] == 'default_bar_option'

The solution

This plugin automagically adds indirect parametrization to tests using selected fixtures, then allows specifying individual parametrization arguments, and nesting them:

@pytest.fixture(indirect=True)  #  HERE
def foo(request):
    return request.param


@pytest.mark.parametrize('foo.first', [1])  # AND HERE
@pytest.mark.parametrize('foo.second', [2])
def test_options(foo):
    assert foo['first'] == 1
    assert bar['second'] == 2


# shorthand syntax
@pytest.mark.foo(first=1)
@pytest.mark.bar(second=2)
def test_options(foo):
    assert foo['first'] == 1
    assert bar['second'] == 2


# all fixture params
@pytest.mark.parametrize('foo.*', [dict(first=1, second=2)])
def test_options(foo):
    assert foo['first'] == 1
    assert bar['second'] == 2

As can be seen in the example, request.param returns a dictionary with keys pulled from parametrize's extended argument name syntax: '<fixture>.<key>'.

Note

For shorthand notation to work, marks still need to be registered.

Warning

Obviously, specifying fixture as indirect=True makes no sense when also passing params=...`.

Having this, you no longer need to mark tests with parametrize(indirect=...):

@pytest.fixture(indirect=True)
def foo(request):
   default = {'option': 'default_foo_option'}
   return {**default, **request.param)


@pytest.fixture(indirect=True)
def bar(paramark):
   default = {'option': 'default_bar_option'}
   return {**default, **request.param)


@pytest.mark.parametrize('foo.option', ['custom_foo_option'])
def test_options(foo, bar):
   assert foo['option'] == 'custom_foo_option'
   assert bar['option'] == 'default_bar_option'

Also, you can group and nest such parametrizations:

@pytest.mark.foo(option=True)
class TestGroup:
    def test_default(self, foo):
        assert foo['option']

    @pytest.mark.foo(option=False)
    def test_override(self, foo):
        assert not foo['option']

or, if you want to be safer and fancier:

import typing


@pytest.fixture(indirect=True)
def foo(request):
   class Foo(typing.NamedTuple):
      option: str = 'default_foo_option'

   return Foo(**request.param)


@pytest.fixture(indirect=True)
def bar(request):
   class Bar(typing.NamedTuple):
      option: str = 'default_bar_option'

   return Bar(**request.param)


@pytest.mark.parametrize('foo.option', ['custom_foo_option'])
def test_options(foo, bar):
   assert foo.option == 'custom_foo_option'
   assert bar.option == 'default_bar_option'

Installation

You can install "pytest-paramark" via pip from PyPI:

$ pip install pytest-paramark

Contributing

Contributions are very welcome. Tests can be run with tox, please ensure the coverage at least stays the same before you submit a pull request.

License

Distributed under the terms of the MIT license, "pytest-paramark" is free and open source software

Issues

If you encounter any problems, please file an issue along with a detailed description.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages