{"record":{"id":"450f4d77e8233b69","repo":"pestphp/pest","slug":"expectation-value-is-not-iterable","errorCode":null,"errorMessage":"Expectation value is not iterable.","messagePattern":"Expectation value is not iterable\\.","errorType":"exception","errorClass":"BadMethodCallException","httpStatus":null,"severity":"error","filePath":"src/Expectation.php","lineNumber":187,"sourceCode":"\n        return $this;\n    }\n\n    /**\n     * @return OppositeExpectation<TValue>\n     */\n    public function not(): OppositeExpectation\n    {\n        return new OppositeExpectation($this);\n    }\n\n    /**\n     * @return EachExpectation<TValue>\n     */\n    public function each(?callable $callback = null): EachExpectation\n    {\n        if (! is_iterable($this->value)) {\n            throw new BadMethodCallException('Expectation value is not iterable.');\n        }\n\n        if (is_callable($callback)) {\n            foreach ($this->value as $key => $item) {\n                $callback(new self($item), $key);\n            }\n        }\n\n        return new EachExpectation($this);\n    }\n\n    /**\n     * @template TSequenceValue\n     *\n     * @param  (callable(self<TValue>, self<string|int>): void)|TSequenceValue  ...$callbacks\n     * @return self<TValue>\n     */\n    public function sequence(mixed ...$callbacks): self","sourceCodeStart":169,"sourceCodeEnd":205,"githubUrl":"https://github.com/pestphp/pest/blob/1af74a215c3a89bc7a44fc6f9322e369eaad2423/src/Expectation.php#L169-L205","documentation":"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.","triggerScenarios":"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().","commonSituations":"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.","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."],"exampleFix":"// before\nexpect($user->roles)->each(fn ($role) => $role->toBeString());\n// after\nexpect($user->roles)->toBeArray();\nexpect($user->roles)->each(fn ($role) => $role->toBeString());","handlingStrategy":"type-guard","validationCode":"if (! is_iterable($value)) {\n    expect($value)->toBeIterableOrArray(); // fails loudly with your own message\n    return;\n}","typeGuard":"function isIterable(mixed $value): bool\n{\n    return is_iterable($value);\n}\n\n// usage\nif (isIterable($items)) {\n    expect($items)->each(fn ($item) => $item->toBeString());\n} else {\n    expect($items)->toBeNull(); // or whatever the scalar case should be\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["pest","expectation","each","iterable","type-error"],"backgroundTag":"operation-on-non-iterable","analyzedSha":"1af74a215c3a89bc7a44fc6f9322e369eaad2423","analyzedAt":"2026-08-21T02:16:38.132Z","schemaVersion":2},"datasetVersion":"2026-08-21T03:17:12.404Z"}