pestphp/pest · error · ExpectationFailedException
Unhandled match value.
Error message
Unhandled match value.
What it means
expect($value)->match($subject, $expressions) walks the expression map and loosely compares each key against the subject; if no key matches, Pest throws ExpectationFailedException('Unhandled match value.') because the expectation cannot proceed. Unlike PHP's match, there is no implicit default arm here — every reachable subject value needs an explicit key.
Source
Thrown at src/Expectation.php:268
if ($subject != $key) { // @pest-arch-ignore-line
continue;
}
$matched = true;
if (is_callable($callback)) {
$callback(new self($this->value));
continue;
}
$this->and($this->value)->toEqual($callback);
break;
}
if ($matched === false) {
throw new ExpectationFailedException('Unhandled match value.');
}
return $this;
}
/**
* @param (callable(): bool)|bool $condition
* @param callable(Expectation<TValue>): mixed $callback
* @return self<TValue>
*/
public function unless(callable|bool $condition, callable $callback): Expectation
{
$condition = is_callable($condition)
? $condition
: static fn (): bool => $condition;
return $this->when(! $condition(), $callback);
}View on GitHub (pinned to 1af74a215c)
Solutions
- Add a map entry whose key equals the subject value reported in the failure.
- Normalize the subject before matching: strtolower()/trim() the subject and keys, or match on a canonical enum ->value.
- If a catch-all is needed, compute it explicitly: $expressions[$subject] ?? $fallbackAssertion instead of relying on a 'default' key that only matches a literal 'default' subject.
- Replace with when()/unless() chains when the set of possible subjects is open-ended.
Example fix
// before
expect($plan)->match($user->status, [
'active' => fn ($v) => $v->toBe('pro'),
]);
// $user->status === 'trial' -> Unhandled match value.
// after
expect($plan)->match($user->status, [
'active' => fn ($v) => $v->toBe('pro'),
'trial' => fn ($v) => $v->toBe('free'),
]); Defensive patterns
Strategy: validation
Validate before calling
$expressions = ['active' => 1, 'blocked' => 0];
$subject = strtolower($user->status);
if (! array_key_exists($subject, $expressions)) {
throw new LogicException("No match arm for status [{$subject}]");
}
expect($plan)->match($subject, $expressions); Prevention
- Keep the subject and the expression keys canonicalized (same case, trimmed) before match().
- When the subject set is open-ended, use a computed fallback ($expressions[$subject] ?? $fallback) instead of relying on match().
- Add a new arm in the same commit that adds a new status/value to production code.
When it happens
Trigger: expect($x)->match($status, ['active' => fn ($v) => $v->toBe(1), 'blocked' => 0]) with $status = 'pending'; a dynamic subject (method return, enum ->value, env flag) that gained a new value not covered by the map; loose-comparison traps such as expecting 0 to match the key '0' differently than intended.
Common situations: Switch-like assertions over statuses, locales, or feature flags where a new case is added to the code but not the test map; data-driven tests where the subject comes from a fixture; typos or casing differences between subject and map keys (loose == does not bridge 'Active' vs 'active').
Related errors
- Expectation value is not iterable.
- No sequence expectations defined.
- Sequence expectations are more than the iterable items.
- Method [%s] does not exist in [%s].
- Expecting %s not %s %s.
AI-assisted analysis of pestphp/pest@1af74a215c (2026-08-21).
Data as JSON: /api/errors/fabe89900882e8b9.
Report an issue: GitHub.