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
- Configure the return explicitly: $stub->method('find')->willReturn('n/a') (pick the union member your subject expects).
- Or provide logic: ->willReturnCallback(fn () => new Foo()) for intersection types.
- For unions including null, willReturn(null) satisfies nullable unions.
- 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
- Never assume auto-generated returns for PHP 8 union/intersection types; always configure them.
- When stubbing an unfamiliar API, scan its methods for union/intersection return types first.
- Wrap auto-generation failures in test helpers that then report which method needs a configured value.
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
- Method %s::%s() is declared to never return
- Return value for %s::%s() cannot be generated: %s
- Cannot double method with invalid name "%s"
- Cannot double using a method list that contains duplicates:
- Cannot use "%s" as the name of a test double class because i
AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23).
Data as JSON: /api/errors/6ba7bb5df7d5d0eb.
Report an issue: GitHub.