NewGuy
NewGuy

Reputation: 3423

How to properly return a list from a pytest fixture for use in parametrize?

I am attempting to build a little pytest test to ensure all expected keys exist in redis. I have a list of expected keys, that I am storing as a YML file. The test itself will query against redis to ensure each of the expected keys from the list exists.

Initially, I had this set up as a giant list in the test_keys.py file. This was set up like so:

expected_keys = ['key1','key2','key3']
@pytest.mark.parametrize('expected_key', expected_keys)
def test_expected_key(expected_key):
    ...

This works. Since I want to replicate this type of test for a few other checks of the redis environment, I don't want to put multiple lists with a few hundred keys into these files.

I thought I could pull them out into YML files and load the keys via fixtures.

My fixture looks like this:

@pytest.fixture
def expected_keys_fixture():
    with open('expected_keys.yml'), 'r') as f:
        return yaml.safe_load(f)['keys']

The YML looks like this:

keys:
  - key1
  - key2
  - key3

My test decorator changed to this:

@pytest.mark.parametrize("expected_keys", [
    (pytest.lazy_fixture('expected_keys_fixture'))
])
def test_expected_key(expected_key):
    ...

I am using the pytest-lazy-fixture package for this.

The problem I have here is that expected_keys now equals the entire list of expected keys. It's no long each individual key like I had with the static list when it was in my test file.

I attempted to do as Oleh Rybalchenko answer suggested

@pytest.mark.parametrize("expected_keys", pytest.lazy_fixture('expected_keys_fixture')
)
def test_expected_key(expected_key):
    ...

But, this fails with TypeError: 'LazyFixture' object is not iterable.

I understand I only have one argname and that I should have a list, but the example in the documentation is passing the parameters to the fixture() decorator. I am not. My list is being generated by the results of the YML file.


Question: How can I adjust my fixture so that it properly returned a single item at a time for the parametrize decorator?

Upvotes: 19

Views: 6036

Answers (5)

Mounir
Mounir

Reputation: 17

To the best of my knowledge, pytest do not let you returning a list of possible test cases. So, I would implement a test loop.

@pytest.mark.parametrize('expected_key', expected_keys)
def test_expected_keys(expected_keys):
    for expected_key in expected_keys:
        test_expected_key()

Upvotes: 1

Jubin Ben
Jubin Ben

Reputation: 29

I was stuck in a similar problem once. I used the pytest global variable, pytest-fixures and pytest-mark-parametrize.

For the use above use-case following code should work fine.

import pytest

@pytest.fixture
def expected_key(request):
    """Create tester object"""
    return request.param

def expected_keys_fixture():
    with open('expected_keys.yml'), 'r') as f:
        return yaml.safe_load(f)['keys']

pytest.expected_keys = expected_keys_fixture()

@pytest.mark.parametrize('expected_key', pytest.expected_keys, indirect=True)
def test_expected_key(expected_key):
    condition = True # Change as per the use case
    assert condition

@pytest.mark.parametrize('expected_key', pytest.expected_keys, indirect=True)
def test_expected_key_2(expected_key):
    condition_2 = True # Change as per the use case
    assert condition

@pytest.mark.parametrize('expected_key', pytest.expected_keys, indirect=True)
def test_expected_key_3(expected_key):
    condition_3 = True # Change as per the use case
    assert condition

The main advantage of defining a Pytest global variable is that you don't have to repeatedly call the function expected_keys_fixture() for each kind of test case. Once the variable is defined, it can be used in multiple kinds of test cases without being reinitialised.

Upvotes: 0

raratiru
raratiru

Reputation: 9626

You can also use the params parameter of the pytest.fixture decorator as shown in the docs

import pytest


def expected_keys():
    with open('expected_keys.yml', 'r') as f:
        return yaml.safe_load(f)['keys']


@pytest.fixture(params=expected_keys())
def expected_key(request):
    yield request.param


def test_keys(expected_key):
    assert expected_key in ['key1', 'key2', 'key3']

Upvotes: 4

Denis Otkidach
Denis Otkidach

Reputation: 33200

The problem is that fixtures are executed after collecting tests, so it's not possible to multiply you tests from fixture result. But you can dynamically parametrize your test with pytest_generate_tests hook.

Upvotes: 0

Roman Sklyarov
Roman Sklyarov

Reputation: 971

In the same situation and the only way I found is to refuse the fixture and call the function directly.

def expected_keys_fixture():
    with open('expected_keys.yml', 'r') as f:
        return yaml.safe_load(f)['keys']


@pytest.mark.parametrize("expected_key",
                         expected_keys_fixture())
def test_expected_key(expected_key):
    assert expected_key in ['key1', 'key2', 'key3']

Upvotes: 11

Related Questions