pestphp/pest · error · BadMethodCallException
Expectation value is not iterable.
Error message
Expectation value is not iterable.
What it means
expect($value)->each(...) first verifies the expectation value is iterable (array or Traversable). If it is not — a scalar, null, or a plain object — Pest throws BadMethodCallException because each() has nothing to iterate. This is a value-type mistake in the expectation chain, not an assertion failure.
Source
Thrown at src/Expectation.php:187
return $this;
}
/**
* @return OppositeExpectation<TValue>
*/
public function not(): OppositeExpectation
{
return new OppositeExpectation($this);
}
/**
* @return EachExpectation<TValue>
*/
public function each(?callable $callback = null): EachExpectation
{
if (! is_iterable($this->value)) {
throw new BadMethodCallException('Expectation value is not iterable.');
}
if (is_callable($callback)) {
foreach ($this->value as $key => $item) {
$callback(new self($item), $key);
}
}
return new EachExpectation($this);
}
/**
* @template TSequenceValue
*
* @param (callable(self<TValue>, self<string|int>): void)|TSequenceValue ...$callbacks
* @return self<TValue>
*/
public function sequence(mixed ...$callbacks): selfView on GitHub (pinned to 1af74a215c)
Solutions
- Assert the value is iterable first, or guard with is_iterable() before the expectation.
- Fix the data source: decode JSON with json_decode($json, true) to get an array, or cast (array) $value when appropriate.
- If the value is legitimately a scalar, replace each() with a direct assertion such as toBe()/toMatch().
- If it can be null, handle the null case separately (toBeNull() or a null-safe branch) before iterating.
Example fix
// before expect($user->roles)->each(fn ($role) => $role->toBeString()); // after expect($user->roles)->toBeArray(); expect($user->roles)->each(fn ($role) => $role->toBeString());
Defensive patterns
Strategy: type-guard
Validate before calling
if (! is_iterable($value)) {
expect($value)->toBeIterableOrArray(); // fails loudly with your own message
return;
} Type guard
function isIterable(mixed $value): bool
{
return is_iterable($value);
}
// usage
if (isIterable($items)) {
expect($items)->each(fn ($item) => $item->toBeString());
} else {
expect($items)->toBeNull(); // or whatever the scalar case should be
} Prevention
- Chain ->toBeArray() (or a type assertion) before ->each() so failures point at the producer, not the expectation API.
- Decode JSON with true as the second argument when you intend arrays.
- Null-check lookups (first(), find()) before passing results into iterable expectations.
When it happens
Trigger: expect('foo')->each(fn ($item) => ...); expect(null)->each(...); expect($object)->each(...) where the object does not implement Traversable (e.g., a plain stdClass or DTO); a function returned null (failed lookup) and the result is fed straight into expect()->each().
Common situations: Chaining ->each() after a collection method that can return null (first(), find()); API responses decoded to objects instead of arrays; refactoring code so a previously-iterable value became a scalar; forgetting that JSON objects decode to stdClass, not array.
Related errors
- Expectation value length is not countable.
- No sequence expectations defined.
- Sequence expectations are more than the iterable items.
- Unhandled match value.
- Method [%s] does not exist in [%s].
AI-assisted analysis of pestphp/pest@1af74a215c (2026-08-21).
Data as JSON: /api/errors/450f4d77e8233b69.
Report an issue: GitHub.