Search code examples
python-3.xpytestyieldfixturescontextmanager

Pytest is skipping post yield of contextmanager when assertion fails


I have a custom contextmanager I use (not a fixture) for setup and cleanup of a test:

@contextmanager
def db_content(*args, **kwargs):
    instance = db_insert( ... )

    yield instance

    db_delete(instance)

def test_my_test():
    with db_content( ... ) as instance:
        #  ...
        assert result

The problem is that when the assertion fails, the db_delete() code - meaning the post yield statements, are not being executed.

I can see that if I use a fixture this does work.

@pytest.fixture
def db_instance():
    instance = db_insert( ... )

    yield instance

    db_delete(instance)

def test_my_test(db_instance):
        #  ...
        assert result

However, fixtures are very inflexible. I would like to pass different arguments to my context each test, while using fixtures would force me to define a different fixture for each case.


Solution

  • contextlib does not execute the post-yield statements if an exception was thrown. This is by design. To make it work you would have to write:

    @contextmanager
    def db_content(*args, **kwargs):
        instance = db_insert( ... )
    
        try:
            yield instance
    
        finally:
            db_delete(instance)
    

    In my opinion this is counter-intuitive as the try is not on the yield itself.

    I took the implementation of contextmanager and made a safe version one that works as I expected, however its an entire code duplication, if anyone has a better workaround I'd love to see it.