sebastianbergmann/phpunit · error · ExpectationFailedException

No entry in the value map matched the invocation of %s::%s()

Error message

No entry in the value map matched the invocation of %s::%s() with parameters (%s)

What it means

ReturnValueMap is the stub behind returnValueMap() (non-strict) and willReturnMap() (strict). On each invocation it looks for a map row whose parameter segment matches the invocation's arguments (same arity, strict === comparison or Constraint evaluation); the last element of the row is the return value. In strict mode (willReturnMap), when no row matches, PHPUnit throws ExpectationFailedException instead of silently returning null, so an unmatched call cannot hide as a null that later fails somewhere else.

Source

Thrown at src/Framework/MockObject/Runtime/Stub/ReturnValueMap.php:64

     */
    public function invoke(Invocation $invocation): mixed
    {
        $parameterCount = count($invocation->parameters());

        foreach ($this->valueMap as $map) {
            if (!is_array($map) || $parameterCount !== (count($map) - 1)) {
                continue;
            }

            $return = array_pop($map);

            if ($this->parametersMatch($map, $invocation->parameters())) {
                return $return;
            }
        }

        if ($this->strict) {
            throw new ExpectationFailedException(
                sprintf(
                    'No entry in the value map matched the invocation of %s::%s() with parameters (%s)',
                    $invocation->className(),
                    $invocation->methodName(),
                    Exporter::shortenedExport($invocation->parameters()),
                ),
            );
        }

        return null;
    }

    /**
     * @param array<mixed> $mapParameters
     * @param array<mixed> $invocationParameters
     */
    private function parametersMatch(array $mapParameters, array $invocationParameters): bool
    {

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Add a map row for the actually-passed parameters (copy them from the exception message, which prints the exact invocation parameters via Exporter::shortenedExport).
  2. Fix arity: every row must have exactly one more element than the method's argument count for that call site, and default arguments must be spelled out in the row.
  3. Use constraints instead of literals in rows (equalTo with loose settings, isType, callback, instanceof) when exact identity cannot be guaranteed.
  4. If unmatched calls returning null is acceptable, use the non-strict returnValueMap() instead of willReturnMap(); better still, switch to willReturnCallback() for full control over matching and defaults.

Example fix

// before
$cache->method('get')
     ->willReturnMap([['user-1', $user], ['user-2', $user2]]);
$cache->get('user-3'); // strict mode -> ExpectationFailedException

// after
$cache->method('get')
     ->willReturnMap([
         ['user-1', $user],
         ['user-2', $user2],
         ['user-3', null],  // explicit fallback row
     ]);
Defensive patterns

Strategy: validation

Validate before calling

// Validate map rows against the method signature before wiring the stub:
$map = [
    ['user-1', $u1],
    ['user-2', $u2],
    ['user-3', null], // default row for any key
];
$paramCount = (new ReflectionMethod(Cache::class, 'get'))->getNumberOfRequiredParameters();
foreach ($map as $row) {
    assert(count($row) === $paramCount + 1, 'each row = params + return value');
}

Type guard

// Narrow a value before putting it into a map row when strict identity matters:
function isCacheableKey(mixed $key): bool
{
    return is_string($key) && preg_match('/^[a-z0-9-]+$/', $key) === 1;
}

Try / catch

use PHPUnit\Framework\ExpectationFailedException;

try {
    $sut->lookup($key);
} catch (ExpectationFailedException $e) {
    if (str_contains($e->getMessage(), 'No entry in the value map matched')) {
        self::fail('Add a willReturnMap row for parameters: ' . $e->getMessage());
    }
    throw $e;
}

Prevention

When it happens

Trigger: $mock->method('get')->willReturnMap([['a', 1], ['b', 2]]) followed by $mock->get('c'): no row's parameter segment equals ['c'], so with strict=true the 'No entry in the value map matched ... with parameters (...)' exception is thrown at call time inside the test. Non-matching rows are silently skipped when their arity differs (count($map) - 1 !== parameterCount) or their values differ under strict comparison.

Common situations: A default-argument call omits a parameter the map rows include (or vice versa), so arity check rejects every row; values that look equal but differ in type (0 vs '0', null vs '', false vs 0) under the strict !== comparison; passing objects that are equal-but-not-identical instances; forgetting that the return value must be the LAST element of each row; enums/named-argument reordering changes what the SUT actually passes.

Related errors


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