37
votes

For an application I'm testing I'd like to create an autouse=True fixture which monkeypatches smtplib.SMTP.connect to fail tests if they try to send an email unexpectedly.

However, in cases where I do expect tests to send emails, I want to use a different fixture logging those emails instead (most likely by using the smtpserver fixture from pytest-localserver and monkeypatching the connect method to use the host/port returned by that fixture)

Of course that can only work if the autouse fixture is executed before the other fixture (loaded as funcarg). Is there any specific order in which fixtures are executed and/or is there a way to guarantee the execution order?

5
A very valid question, i've seen fixtures been abused many time, and one of the most problematic thing is which fixture run before which one - Fruch
An alternative trick that is useful to know is that fixtures can inspect test functions for pytest marks. This means you can @mark the tests that need to do something special, and then, using the request object, inspect if the test function has the mark. If it does, perform a different action in your fixture. - user2859458

5 Answers

37
votes

The easiest way to control the order in which fixtures are executed, is to just request the previous fixture in the later fixture. So to make sure b runs before a:

@pytest.fixture(autouse=True, scope="function")
def b():
    pass

@pytest.fixture(scope="function")
def a(b):
    pass

For details on the general fixture resolution order, see Maxim's excellent answer below.

10
votes

I was just having this problem with two function-scoped autouse fixtures. I wanted fixture b to run before fixture a, but every time, a ran first. I figured maybe it was alphabetical order, so I renamed a to c, and now b runs first. Pytest doesn't seem to have this documented. It was just a lucky guess. :-)

That's for autouse fixtures. Considering broader scopes (eg. module, session), a fixture is executed when pytest encounters a test that needs it. So if there are two tests, and the first test uses a session-scoped fixture named sb and not the one named sa, then sb will get executed first. When the next test runs, it will kick off sa, assuming it requires sa.

7
votes

TL;DR

There are 3 aspects being considered together when building fixture evaluation order, aspects themselves are placed in order of priority:

  • Fixture dependencies - fixtures are evaluated from rootest required back to one required in test function.
  • Fixture scope - fixtures are evaluated from session through module to function scoped. On the same scope autouse fixtures are evaluated before non-autouse ones.
  • Fixture position in test function arguments - fixtures are evaluated in order of presence in test function arguments, from leftest to rightest.

Official explanation with code example by link below

https://docs.pytest.org/en/stable/fixture.html#order-higher-scoped-fixtures-are-instantiated-first

3
votes

IIRC you can rely on higher scoped fixtures to be executed first. So if you created a session scoped autouse fixture to monkeypatch smtplib.SMTP.connect then you could create a function-scoped fixture which undoes this monkeypatching for one test, restoring it afterwards. I assume the easiest way to do this is create your own smtpserver fixture which depends on both the disallow_smtp fixture as well as the smtpserver fixture from pytest-localserver and then handles all setup and teardown required to make these two work together.

This is vaguely how pytest-django handles it's database access btw, you could try and look at the code there but it is far from a simple example and has many of it's own weird things.

-2
votes

With the help of below code we can easily set execution order of fixtures / functions

e.g:-

execution order first

@pytest.mark.order(1)

execution order second

@pytest.mark.order(2)