sebastianbergmann/phpunit · error · RuntimeException

Return value for %s::%s() cannot be generated%s, please conf

Error message

Return value for %s::%s() cannot be generated%s, please configure a return value for this method

What it means

Thrown by ReturnValueGenerator::generate() when a stubbed method has no configured return value and the declared return type cannot be auto-generated: a union type (reason ' because the declared return type is a union') or an intersection type (reason '... is an intersection'). PHPUnit cannot pick a member of a union or fabricate an intersection instance, so it demands an explicit willReturn(...).

Source

Thrown at src/Framework/MockObject/Runtime/ReturnValueGenerator.php:153

                        return $this->testDoubleForIntersectionOfInterfaces($_types, $className, $methodName);
                    }
                }
            }
        }

        if ($intersection && $this->onlyInterfaces($types)) {
            return $this->testDoubleForIntersectionOfInterfaces($types, $className, $methodName);
        }

        $reason = '';

        if ($union) {
            $reason = ' because the declared return type is a union';
        } elseif ($intersection) {
            $reason = ' because the declared return type is an intersection';
        }

        throw new RuntimeException(
            sprintf(
                'Return value for %s::%s() cannot be generated%s, please configure a return value for this method',
                $className,
                $methodName,
                $reason,
            ),
        );
    }

    /**
     * @param non-empty-list<string> $types
     */
    private function onlyInterfaces(array $types): bool
    {
        return array_all($types, static fn (string $type) => interface_exists($type));
    }

    /**

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Configure the return explicitly: $stub->method('find')->willReturn('n/a') (pick the union member your subject expects).
  2. Or provide logic: ->willReturnCallback(fn () => new Foo()) for intersection types.
  3. For unions including null, willReturn(null) satisfies nullable unions.
  4. If the method should not be called, add expects($this->never()) to make the intent (and failure) explicit.

Example fix

// before
$store = $this->createStub(Store::class);
$subject->load($store); // load() calls read(): string|Stream — unconfigured

// after
$store = $this->createStub(Store::class);
$store->method('read')->willReturn('stub-content');
$subject->load($store);
Defensive patterns

Strategy: validation

Validate before calling

// refuse to rely on generation for union/intersection returns
$rm = new ReflectionMethod(Repo::class, 'find');
$type = $rm->getReturnType();
if ($type instanceof ReflectionUnionType || $type instanceof ReflectionIntersectionType) {
    $stub->method('find')->willReturn('default'); // explicit member of the union
}

Type guard

function needsExplicitReturn(ReflectionMethod $m): bool
{
    $t = $m->getReturnType();

    return $t instanceof ReflectionUnionType
        || $t instanceof ReflectionIntersectionType;
}

Try / catch

try {
    $subject->load($stub);
} catch (PHPUnit\Framework\MockObject\RuntimeException $e) {
    // message names class::method — add ->method(...)->willReturn(...) and rerun
}

Prevention

When it happens

Trigger: createStub(Repo::class) where a method is declared : int|string (or : Foo&Bar) and the test never calls ->method(...)->willReturn(...) before the subject invokes it; native return types like :iterable|Countable growing common in PHP 8+ APIs.

Common situations: Upgrading a dependency whose methods changed from concrete types to unions (e.g. string|Stringable); partial stubbing where an untouched method is suddenly called by a code path; using stubs as 'smart defaults' and hitting a newly added union-typed getter.

Related errors


AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23). Data as JSON: /api/errors/6ba7bb5df7d5d0eb. Report an issue: GitHub.