3

I am trying to use pytest-dependency to make fixtures happen in order, regardless of how they are named, and regardless of the order in which they appear in a test's argument list.

The reason I need this, is to create fixtures that require initializations, that depend on other fixtures that require initializations, and they must happen in order. I have quite a few of these, and I don't want to rely on naming or on order in the argument list.

I also don't want to use pytest_sessionstart, because it can't take fixture inputs, which causes very unclean code.


The trivial example in the doc shows how to create programmed dependencies for tests:

import pytest

@pytest.mark.dependency()
@pytest.mark.xfail(reason="deliberate fail")
def test_a():
    assert False

@pytest.mark.dependency()
def test_b():
    pass

@pytest.mark.dependency(depends=["test_a"])
def test_c():
    pass

@pytest.mark.dependency(depends=["test_b"])
def test_d():
    pass

@pytest.mark.dependency(depends=["test_b", "test_c"])
def test_e():
    pass

This works with output:

============================= test session starts =============================
collecting ... collected 5 items

test_sanity.py::test_z XFAIL (deliberate fail)                           [ 20%]
@pytest.mark.dependency()
    @pytest.mark.xfail(reason="deliberate fail")
    def test_z():
>       assert False
E       assert False

test_sanity.py:6: AssertionError

test_sanity.py::test_x PASSED                                            [ 40%]
test_sanity.py::test_c SKIPPED (test_c depends on test_z)                [ 60%]
Skipped: test_c depends on test_z

test_sanity.py::test_d PASSED                                            [ 80%]
test_sanity.py::test_w SKIPPED (test_w depends on test_c)                [100%]
Skipped: test_w depends on test_c


=================== 2 passed, 2 skipped, 1 xfailed in 0.05s ===================

Now I want to do the same for fixtures.

My attempt:

conftest.py:

import pytest

pytest_plugins = ["dependency"]


@pytest.mark.dependency()
@pytest.fixture(autouse=True)
def zzzshould_happen_first():
    assert False


@pytest.mark.dependency(depends=["zzzshould_happen_first"])
@pytest.fixture(autouse=True)
def should_happen_last():
    assert False

and test_sanity.py:

def test_nothing():
    assert True

Outputs

test_sanity.py::test_nothing ERROR                                       [100%]
test setup failed
@pytest.mark.dependency(depends=["zzzshould_happen_first"])
    @pytest.fixture(autouse=True)
    def should_happen_last():
>       assert False
E       assert False

conftest.py:15: AssertionError

I expected the error on zzzshould_happen_first.


Is there a way to impose ordering on fixtures, such that

  1. Their name is ignored
  2. Their order in the argument list is ignored
  3. Other pytest features such as autouse can still be applied
Gulzar
  • 23,452
  • 27
  • 113
  • 201

1 Answers1

4

You can give a fixture as a dependency directly with pytest. Something like this:

import pytest


@pytest.fixture(autouse=True)
def zzzshould_happen_first():
    assert False


@pytest.fixture(autouse=True)
def should_happen_last(zzzshould_happen_first):
    assert False


def test_nothing():
    assert True

It gives what you want:

test setup failed
@pytest.fixture(autouse=True)
    def zzzshould_happen_first():
>       assert False
E       assert False

answer.py:7: AssertionError
ndclt
  • 2,590
  • 2
  • 12
  • 26
  • Thanks, this is indeed the trivial solution! I am sorry I have to add a detail now that didn't occur to me: I am using [a plugin](https://github.com/pytest-docker-compose/pytest-docker-compose#:~:text=This%20package%20contains%20a%20pytest,down%20after%20each%20test%20ends.) that adds its own fixtures. They require external initialization, but I can't change them. I am aware that it is an entirely different question, but maybe you have an idea? I want to force my own fixture to run before any other fixture, then I will be able to use this proposed method. – Gulzar Feb 09 '22 at 12:34
  • Ideally, you should be able to give a function to the fixture that would be called before. But maybe your use case doesn't need to change anything to the plugin. I think you should ask the question with some details about what you try to achieve (mainly why you need to do something before loading the docker-compose). – ndclt Feb 09 '22 at 12:59
  • What I need before docker-compose up is... docker-compose down (and network remove, and network create). The library doesn't clean up in case the environment is not clean to begin with, so I have to do that before it comes into play. – Gulzar Feb 09 '22 at 13:12
  • In this case, I think you should fork the initial project (or open an issue) in order to add your functionality. – ndclt Feb 09 '22 at 17:11