Passing (yield) fixtures as test parameters (with a temp directory)

Viewed 5138

Question

Is it possible to pass yielding pytest fixtures (for setup and teardown) as parameters to test functions?

Context

I'm testing an object that reads and writes data from/to files in a single directory. That path of that directory is saved as an attribute of the object.

I'm having trouble with the following:

  1. using a temporary directory with my test; and
  2. ensuring that the directory is removed after each test.

Example

Consider the following (test_yieldfixtures.py):

import pytest, tempfile, os, shutil
from contextlib import contextmanager
    
@contextmanager
def data():
    datadir = tempfile.mkdtemp()  # setup
    yield datadir
    shutil.rmtree(datadir)        # teardown
    
class Thing:
    def __init__(self, datadir, errorfile):
        self.datadir = datadir
        self.errorfile = errorfile

    
@pytest.fixture
def thing1():
    with data() as datadir:
        errorfile = os.path.join(datadir, 'testlog1.log')
        yield Thing(datadir=datadir, errorfile=errorfile)

@pytest.fixture
def thing2():
    with data() as datadir:
        errorfile = os.path.join(datadir, 'testlog2.log')
        yield Thing(datadir=datadir, errorfile=errorfile)

@pytest.mark.parametrize('thing', [thing1, thing2])
def test_attr(thing):
    print(thing.datadir)
    assert os.path.exists(thing.datadir)

Running pytest test_yieldfixtures.py outputs the following:

================================== FAILURES ===================================
______________________________ test_attr[thing0] ______________________________

thing = <generator object thing1 at 0x0000017B50C61BF8>

    @pytest.mark.parametrize('thing', [thing1, thing2])
    def test_attr(thing):
>        print(thing.datadir)
E       AttributeError: 'function' object has no attribute 'props'

test_mod.py:39: AttributeError

OK. So fixture functions don't have a the properties of my class. Fair enough.

Attempt 1

A function won't have the properties, so I tried calling that functions to actually get the objects. However, that just

@pytest.mark.parametrize('thing', [thing1(), thing2()])
def test_attr(thing):
    print(thing.props['datadir'])
    assert os.path.exists(thing.get('datadir'))

Results in:

================================== FAILURES ===================================
______________________________ test_attr[thing0] ______________________________

thing = <generator object thing1 at 0x0000017B50C61BF8>

    @pytest.mark.parametrize('thing', [thing1(), thing2()])
    def test_attr(thing):
>       print(thing.datadir)
E       AttributeError: 'generator' object has no attribute 'props'

test_mod.py:39: AttributeError

Attempt 2

I also tried using return instead of yield in the thing1/2 fixtures, but that kicks me out of the data context manager and removes the directory:

================================== FAILURES ===================================
______________________________ test_attr[thing0] ______________________________

thing = <test_mod.Thing object at 0x000001C528F05358>

    @pytest.mark.parametrize('thing', [thing1(), thing2()])
    def test_attr(thing):
        print(thing.datadir)
>       assert os.path.exists(thing.datadir)

Closing

To restate the question: Is there anyway to pass these fixtures as parameters and maintain the cleanup of the temporary directory?

3 Answers
Related