Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In pytest, how can I abort the fixture teardown?

Tags:

python

pytest

Our pytest environment has a lot of fixtures (mostly scope='function' and scope='module') that are doing something of the form:

@pytest.yield_fixture(scope='function')
def some_fixture():
    ... some object initialization ...
    yield some_object
    ... teardown ...

We use the teardown phase of the fixture (after the yield) to delete some resources created specifically for the test.

However, if a test is failing, I don't want the teardown to execute so we will have the resources still exist for further debugging.

For example, here is a common scenario that repeats in all of our testing framework:

@pytest.yield_fixture(scope='function')
def obj_fixture():
    obj = SomeObj.create()
    yield obj
    obj.delete()

def test_obj_some_field(obj_fixture):
    assert obj_fixture.some_field is True

In this case, if the condition in the assert is True I want the obj.delete() to execute. However, if the test is failing, I want pytest to skip the obj.delete() and anything else after the yield.

Thank you.

EDIT I want the process to be done without altering the fixture and the tests code, I prefer an automatic process instead of doing this refactor in our whole testing codebase.

like image 742
Tomer Gal Avatar asked Dec 09 '25 01:12

Tomer Gal


1 Answers

There's an example in the pytest docs about how to do this. The basic idea is that you need to capture this information in a hook function and add it to the test item, which is available on the test request, which is available to fixtures/tests via the request fixture.

For you, it would look something like this:

# conftest.py

import pytest

@pytest.hookimpl(tryfirst = True, hookwrapper = True)
def pytest_runtest_makereport(item, call):
    # execute all other hooks to obtain the report object
    outcome = yield
    rep = outcome.get_result()

    # set a report attribute for each phase of a call, which can
    # be "setup", "call", "teardown"

    setattr(item, "rep_" + rep.when, rep)
# test_obj.py

import pytest


@pytest.fixture()
def obj(request):
    obj = 'obj'
    yield obj

    # setup succeeded, but the test itself ("call") failed
    if request.node.rep_setup.passed and request.node.rep_call.failed:
        print(' dont kill obj here')
    else:
        print(' kill obj here')


def test_obj(obj):
    assert obj == 'obj'
    assert False # force the test to fail

If you run this with pytest -s (to not let pytest capture output from fixtures), you'll see output like

foobar.py::test_obj FAILED dont kill obj here

which indicates that we're hitting the right branch of the conditional.

like image 166
Josh Karpel Avatar answered Dec 10 '25 14:12

Josh Karpel