pestphp/pest · error · BadMethodCallException
Expectation value length is not countable.
Error message
Expectation value length is not countable.
What it means
toHaveLength() supports three value kinds: strings (mb_strlen comparison), iterables (delegated to toHaveCount), and objects (toArray() if present, else cast to array and counted). For anything else — int, float, bool, null — there is no meaningful length, so Pest throws BadMethodCallException instead of guessing. Note the sibling toHaveCount() has its own stricter guard for countable|iterable values.
Source
Thrown at src/Mixins/Expectation.php:240
if (is_string($this->value)) {
Assert::assertEquals($number, mb_strlen($this->value), $message);
return $this;
}
if (is_iterable($this->value)) {
return $this->toHaveCount($number, $message);
}
if (is_object($this->value)) {
$array = method_exists($this->value, 'toArray') ? $this->value->toArray() : (array) $this->value;
Assert::assertCount($number, $array, $message);
return $this;
}
throw new BadMethodCallException('Expectation value length is not countable.');
}
/**
* @return self<TValue>
*/
public function toHaveCount(int $count, string $message = ''): self
{
if (! is_countable($this->value) && ! is_iterable($this->value)) {
InvalidExpectationValue::expected('countable|iterable');
}
Assert::assertCount($count, $this->value, $message);
return $this;
}
/**
* @param Countable|iterable<mixed> $expectedView on GitHub (pinned to 1af74a215c)
Solutions
- Cast to string when you mean character length: expect((string) $value)->toHaveLength(5).
- If you mean item count, keep the value iterable/countable and use toHaveCount() instead.
- Handle null separately (expect($value)->not->toBeNull()) before asserting length.
- Fix the producer to return the string form (e.g., (string) $id) when length semantics matter.
Example fix
// before expect($zipCode)->toHaveLength(5); // $zipCode is int 12345 // after expect((string) $zipCode)->toHaveLength(5); // or fix the producer to keep it a string
Defensive patterns
Strategy: type-guard
Validate before calling
$value = $api->zipCode(); // could be int|string
if (is_int($value)) {
$value = (string) $value;
}
expect($value)->toHaveLength(5); Type guard
function hasLength(mixed $value): bool
{
return is_string($value) || is_iterable($value) || is_object($value);
}
// usage
if (hasLength($value)) {
expect($value)->toHaveLength(5);
} else {
expect($value)->toBeNull(); // or handle scalar case explicitly
} Prevention
- Cast numeric values to string when asserting character length: expect((string) $value)->toHaveLength(n).
- Null-check producers before length assertions.
- Use toHaveCount() for collections and toHaveLength() for strings to keep type intent explicit.
When it happens
Trigger: expect(42)->toHaveLength(2); expect(null)->toHaveLength(0); expect(3.14)->toHaveLength(4); expect(true)->toHaveLength(...); passing a value that a producer typed as int (strlen result, count result) when a string was intended.
Common situations: Counting digits and forgetting to cast: expect(12345)->toHaveLength(5) instead of expect((string) 12345); null returned by a lookup fed directly into the chain; strict-types codebases where an int sneaks past reviews; numbers coming from APIs as int rather than numeric strings.
Related errors
- Expectation value is not iterable.
- 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/c71ee86a80dfc8cf.
Report an issue: GitHub.