pytest autouse=True not working correctly? simply returning ["a"]

1.2k Views Asked by At

Here's the code

import pytest


@pytest.fixture
def first_entry():
    return "a"

@pytest.fixture(autouse=True)
def append_first(first_entry):
    return [first_entry]


def test_string_only(first_entry):
    assert first_entry == ["a"]

simply modified from here

I've been staring at it for 40 minutes, cannot figure out why in test_string_only it still recognize first_entry as "a" instead of ["a"] ? I thought the fixture with autouse=True will be executed in each test case?

error logs

======================================================= FAILURES ========================================================
___________________________________________________ test_string_only ____________________________________________________

first_entry = 'a'

    def test_string_only(first_entry):
>       assert first_entry == ["a"]
E       AssertionError: assert 'a' == ['a']

test_fixture2.py:24: AssertionError
1

There are 1 best solutions below

3
On BEST ANSWER

I'll make an answer from my comments, as this may indeed be confusing for more people.

The problem here is the example in the pytest documentation mentioned in the question, that was used as a template:

@pytest.fixture
def first_entry():
    return "a"

@pytest.fixture
def order(first_entry):
    return []

@pytest.fixture(autouse=True)
def append_first(order, first_entry):
    return order.append(first_entry)

def test_string_only(order, first_entry):
    assert order == [first_entry]

This is not a good example to base your code on. What happens here is that the fixture order returns an empty array, and the fixture append_first adds an entry to this array. Due to the fact that append_first is always called, it always appends that entry to the array instance returned from the first fixture. The fact that it also returns it is irrelevant for this case - it would only be needed if the fixture was called directly.

So if using instead:

@pytest.fixture(autouse=True)
def append_first(order, first_entry):
    order.append(first_entry)
    # does not return anything

would have the same effect, namely the side effect of altering the array object returned by the first fixture.

This is unexpected and confusing, and very similar in my opinion to the known anti-pattern of passing an array as a default argument.

As mentioned, fixtures with autouse=True make only sense if they have a side effect, and they usually don't return anything. They may return a value, but that value will only be used if the fixture is used directly, otherwise it will be discarded.

In your example, you simply don't need an autouse fixture, but can just reference the fixture directly, as already mentioned in the other answer:

@pytest.fixture
def first_entry():
    return "a"


@pytest.fixture
def append_first(first_entry):
    return [first_entry]


def test_string_only(append_first):
    assert append_first == ["a"]

Note also that the mentioned pytest example has been changed in the current documentation to not return a value, and received a better explanation.