sebastianbergmann/phpunit · error · IncompatibleReturnValueException

Method %s may not return value of type %s, its declared retu

Error message

Method %s may not return value of type %s, its declared return type is "%s"

What it means

Thrown by ensureTypeOfReturnValues() when a value passed to willReturn() is not accepted by the doubled method's declared return type (ConfigurableMethod::mayReturn()). PHPUnit only checks this when it knows the method's return type (configuredMethod !== null); values that pass PHP's own type checks are required, so this is a static type-compatibility guard before the stub is ever called.

Source

Thrown at src/Framework/MockObject/Runtime/AbstractInvocationImplementation.php:299

        return $_valueMap;
    }

    /**
     * @param array<mixed> $values
     *
     * @throws IncompatibleReturnValueException
     */
    private function ensureTypeOfReturnValues(array $values): void
    {
        $configuredMethod = $this->configuredMethod();

        if ($configuredMethod === null) {
            return;
        }

        foreach ($values as $value) {
            if (!$configuredMethod->mayReturn($value)) {
                throw new IncompatibleReturnValueException(
                    $configuredMethod,
                    $value,
                );
            }
        }
    }
}

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Return a value of the declared type: cast, construct the right class, or use a matching literal.
  2. For nullable return types, willReturn(null) is fine; otherwise pick a real value.
  3. When the value is computed, use ->willReturnCallback(fn () => ...) and let PHP's type system verify the callback's return.
  4. If the production signature changed, update the stub to the new return type instead of suppressing the check.

Example fix

// before
/** @return int */
public function count(): int;
$stub->method('count')->willReturn('42');

// after
$stub->method('count')->willReturn(42);
Defensive patterns

Strategy: type-guard

Validate before calling

$rm = new ReflectionMethod(C::class, 'getCount');
$value = 42;
$ok = $value === null && $rm->getReturnType()->allowsNull();
// or simply assert before stubbing:
assert($value instanceof ($rm->getReturnType()->getName()) || !is_object($value));

Type guard

function valueMatchesReturnType(ReflectionMethod $m, mixed $value): bool
{
    $type = $m->getReturnType();
    if ($type === null) {
        return true;
    }
    if ($value === null) {
        return $type->allowsNull();
    }

    return match ($type->getName()) {
        'int' => is_int($value),
        'string' => is_string($value),
        'bool' => is_bool($value),
        'float' => is_float($value) || is_int($value),
        default => $value instanceof $type->getName(),
    };
}

Try / catch

try {
    $mock->method('getCount')->willReturn($value);
} catch (PHPUnit\Framework\MockObject\IncompatibleReturnValueException $e) {
    // coerce or rebuild the value to satisfy the declared return type
}

Prevention

When it happens

Trigger: ->method('getCount')->willReturn('5') on a method declared : int; ->willReturn(null) on a non-nullable return type; ->willReturn(new Foo) where the method returns Bar; willReturnConsecutiveCalls() entries where one element violates the type.

Common situations: Tests written before a return type was added to production code (tightening types in refactors breaks them); stubbing with string literals for int/float methods under strict comparisons; returning null from a stub for a method made non-nullable; copying expectations between similar classes with different return types.

Related errors


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