{"record":{"id":"40e0a42afafea7bf","repo":"sebastianbergmann/phpunit","slug":"only-d-return-values-have-been-configured-for-s","errorCode":null,"errorMessage":"Only %d return values have been configured for %s::%s()","messagePattern":"Only (.+?) return values have been configured for (.+?)::(.+?)\\(\\)","errorType":"exception","errorClass":"NoMoreReturnValuesConfiguredException","httpStatus":null,"severity":"error","filePath":"src/Framework/MockObject/Runtime/Stub/ConsecutiveCalls.php","lineNumber":45,"sourceCode":"    private array $stack;\n    private int $numberOfConfiguredReturnValues;\n\n    /**\n     * @param array<mixed> $stack\n     */\n    public function __construct(array $stack)\n    {\n        $this->stack                          = $stack;\n        $this->numberOfConfiguredReturnValues = count($stack);\n    }\n\n    /**\n     * @throws NoMoreReturnValuesConfiguredException\n     */\n    public function invoke(Invocation $invocation): mixed\n    {\n        if ($this->stack === []) {\n            throw new NoMoreReturnValuesConfiguredException(\n                $invocation,\n                $this->numberOfConfiguredReturnValues,\n            );\n        }\n\n        $value = array_shift($this->stack);\n\n        if ($value instanceof Stub) {\n            $value = $value->invoke($invocation);\n        }\n\n        return $value;\n    }\n}\n","sourceCodeStart":27,"sourceCodeEnd":60,"githubUrl":"https://github.com/sebastianbergmann/phpunit/blob/f123cdb2a2d49f15025794166cfed8bda8627dd2/src/Framework/MockObject/Runtime/Stub/ConsecutiveCalls.php#L27-L60","documentation":"ConsecutiveCalls is the stub behind willReturnOnConsecutiveCalls() (and deprecated withConsecutive()/willReturnMap-style sequential stubbing): it hands out one configured return value per invocation, in order. When the mocked method is invoked more times than the number of values you configured, the internal stack is empty and PHPUnit throws NoMoreReturnValuesConfiguredException, reporting how many values were originally configured so you can see the mismatch between configured values and actual call count.","triggerScenarios":"$mock->method('fetch')->willReturnOnConsecutiveCalls('a', 'b') followed by three or more calls to $mock->fetch(): the first two calls consume 'a' and 'b', the third call finds $this->stack === [] in ConsecutiveCalls::invoke() and throws. Typical with loops, retries, pagination, or queue-draining code under test that iterates more often than the test's value list covers.","commonSituations":"Retry logic that now attempts one extra round; a paginator that keeps calling until null/empty is returned while the test forgot to append a terminating return value (null, false, []); adding a warm-up call in a refactor; data-provider datasets where one case produces more iterations than the consecutive values list anticipates.","solutions":["Append the missing trailing return value(s) — most often a terminating value such as null, false, or [] so loops in the SUT stop calling.","Replace willReturnOnConsecutiveCalls() with willReturnCallback() or returnValueMap() when call count is unpredictable: map arguments to returns, or generate values until a stop condition.","Reduce the number of calls the SUT makes (fix the loop/retry bound in production code) if the extra call is itself the defect.","For generated mocks, keep auto return value generation in mind: only methods with an explicit consecutive stub consume the stack, so narrowing onlyMethods() can stop the exhausted stub from being hit."],"exampleFix":"// before\n$api->method('loadPage')\n    ->willReturnOnConsecutiveCalls($page1, $page2);\n// SUT loops until null -> third call throws NoMoreReturnValuesConfiguredException\n\n// after\n$api->method('loadPage')\n    ->willReturnOnConsecutiveCalls($page1, $page2, null); // terminator stops the loop","handlingStrategy":"validation","validationCode":"// Guard before the SUT runs when call count is data-driven:\n$iterations = count($items) + 1; // how many times fetch() will be called\n$values     = array_map(fn ($i) => $pages[$i] ?? null, range(0, $iterations - 1));\n$api->method('loadPage')->willReturnOnConsecutiveCalls(...$values);","typeGuard":null,"tryCatchPattern":"use PHPUnit\\Framework\\MockObject\\NoMoreReturnValuesConfiguredException;\n\ntry {\n    $result = $sut->drain();\n} catch (NoMoreReturnValuesConfiguredException $e) {\n    self::fail('Stub ran out of values: ' . $e->getMessage());\n}","preventionTips":["Always end consecutive value lists with the SUT's loop terminator (null, false, []).","Compute the value list from the same count the SUT will iterate, not a hand-typed literal.","Prefer willReturnCallback() when the number of calls is open-ended.","When adding a warm-up/seed call to the SUT, prepend (not append) its return value to every consecutive list in affected tests."],"tags":["phpunit","mockobject","stub","consecutive-calls","return-values"],"backgroundTag":"mock-stub-out-of-values","analyzedSha":"f123cdb2a2d49f15025794166cfed8bda8627dd2","analyzedAt":"2026-08-23T01:20:58.058Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}