Search code examples
pythonpytestgeneratorfactoryfixtures

Yielding from a factory generated by a pytest fixture


The following pytest fixture

@pytest.fixture(scope="module")
def gen_factory():
    def _gen_factory(owner: str):

        value = ...

        yield value

        print('gen teardown')

    yield _gen_factory

yields a factory used multiple times.

In a test case, I use that fixture to create two factory and use them to produce some values:

@pytest.mark.asyncio
def test_case(gen_factory):
    gen1 = gen_factory('owner1')
    gen2 = gen_factory('owner2')
    val1 = next(gen1)
    val2 = next(gen2)

    ...

    next(gen1)
    next(gen2)

What happens is that the print('gen teardown') is called only once and then the loop is closed and the second next() call raises a StopIteration error.

What am I missing here? Why is the second print not happening?


Solution

  • If you don't want to change anything in the fixture, just change the last calls to next:

    next(gen1, None)
    

    With a default value it won't raise StopIteration.

    An alternative is to make _gen_factory a context manager:

    from contextlib import contextmanager
    
    def gen_factory():
        @contextmanager
        def _gen_factory(owner):
            value = owner
            yield value
            print('gen teardown')
        yield _gen_factory
        
    
    def test_case(genfactory):   
        with genfactory('owner1') as own1, genfactory('owner2') as own2:
            print(own1, own2)
    

    The error happens on the (last but one) line having the second call to next(gen1), before it executes the last statement (next(gen2)). The iterator is exhausted. That's how generators work. StopIteration is raised at the end of the generator, (That's why the first one is printed!) once they have no items to be iterated over, unless a default value is passed to the builtin next function. StopIteration makes the for loop stop. Although generators can be used in different ways, their use is primarily to be iterated over in a for loop. That's why, by default, this 'exception' is raised.

    See this tutorial on generators.