pytest-dev/pytest · error · ValueError

{request.fixturename} did not yield a value

Error message

{request.fixturename} did not yield a value

What it means

Raised in `call_fixture_func` when a generator-style fixture function (one containing `yield`) returns without ever yielding a value (StopIteration on the first `next()`). pytest treats generator fixtures as setup/teardown pairs and requires exactly one yielded value, so a generator that exits before yielding has no setup value to inject.

Source

Thrown at src/_pytest/fixtures.py:1061

            for line in lines[1:]:
                tw.line(
                    f"{ExceptionInfoFormatter.flow_marker}       {line.strip()}",
                    red=True,
                )
        tw.line()
        tw.line(f"{os.fspath(self.filename)}:{self.firstlineno + 1}")


def call_fixture_func(
    fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs
) -> FixtureValue:
    if inspect.isgeneratorfunction(fixturefunc):
        fixturefunc = cast(Callable[..., Generator[FixtureValue]], fixturefunc)
        generator = fixturefunc(**kwargs)
        try:
            fixture_result = next(generator)
        except StopIteration:
            raise ValueError(f"{request.fixturename} did not yield a value") from None
        finalizer = functools.partial(_teardown_yield_fixture, fixturefunc, generator)
        request.addfinalizer(finalizer)
    else:
        fixturefunc = cast(Callable[..., FixtureValue], fixturefunc)
        fixture_result = fixturefunc(**kwargs)
    return fixture_result


def _teardown_yield_fixture(fixturefunc, it) -> None:
    """Execute the teardown of a fixture function by advancing the iterator
    after the yield and ensure the iteration ends (if not it means there is
    more than one yield in the function)."""
    try:
        next(it)
    except StopIteration:
        pass
    else:
        fs, lineno = getfslineno(fixturefunc)

View on GitHub (pinned to 98b357f69e)

Solutions

  1. Ensure the generator always reaches exactly one `yield` on every code path; move guards outside the fixture or use pytest.mark.skip.
  2. If you sometimes have nothing to provide, yield a sentinel (e.g. `yield None`) unconditionally.
  3. Split conditional setup into a non-generator fixture returning a value and a separate teardown via `request.addfinalizer`.

Example fix

// before
@pytest.fixture
def conn(request):
    if not ENABLED:
        return                 # -> did not yield a value
    c = open()
    yield c
    c.close()
// after
@pytest.fixture
def conn(request):
    if not ENABLED:
        pytest.skip("disabled")
    c = open()
    yield c
    c.close()
Defensive patterns

Strategy: validation

Validate before calling

import inspect

def safe_fixture(fn):
    if inspect.isgeneratorfunction(fn):
        gen = fn()
        try:
            next(gen)
        except StopIteration:
            raise ValueError(f"{fn.__name__} did not yield")
    return fn

Type guard

def always_yields(fn) -> bool:
    import ast, inspect
    if not inspect.isgeneratorfunction(fn):
        return True
    tree = ast.parse(inspect.getsource(fn))
    return any(isinstance(n, ast.Yield) for n in ast.walk(tree))

Prevention

When it happens

Trigger: A `@pytest.fixture` generator function that conditionally returns before the yield, e.g. `def f():\n if skip: return\n yield 1`, or an accidentally empty generator. The first `next()` raises StopIteration which pytest converts to ValueError naming the fixture.

Common situations: Adding an early `return` guard for a conditional skip inside a yielding fixture. Refactoring a fixture so the yield is inside an `if` branch. Misplaced `return` instead of `continue`.

Related errors


AI-assisted analysis of pytest-dev/pytest@98b357f69e (2026-08-04). Data as JSON: /data/errors/a3107118311618bb.json. Report an issue: GitHub.