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

  1. Add a map entry whose key equals the subject value reported in the failure.
  2. Normalize the subject before matching: strtolower()/trim() the subject and keys, or match on a canonical enum ->value.
  3. 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.
  4. 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

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


AI-assisted analysis of pestphp/pest@1af74a215c (2026-08-21). Data as JSON: /api/errors/fabe89900882e8b9. Report an issue: GitHub.