sebastianbergmann/phpunit · error · NoMoreReturnValuesConfiguredException

Only %d return values have been configured for %s::%s()

Error message

Only %d return values have been configured for %s::%s()

What it means

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.

Source

Thrown at src/Framework/MockObject/Runtime/Stub/ConsecutiveCalls.php:45

    private array $stack;
    private int $numberOfConfiguredReturnValues;

    /**
     * @param array<mixed> $stack
     */
    public function __construct(array $stack)
    {
        $this->stack                          = $stack;
        $this->numberOfConfiguredReturnValues = count($stack);
    }

    /**
     * @throws NoMoreReturnValuesConfiguredException
     */
    public function invoke(Invocation $invocation): mixed
    {
        if ($this->stack === []) {
            throw new NoMoreReturnValuesConfiguredException(
                $invocation,
                $this->numberOfConfiguredReturnValues,
            );
        }

        $value = array_shift($this->stack);

        if ($value instanceof Stub) {
            $value = $value->invoke($invocation);
        }

        return $value;
    }
}

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Append the missing trailing return value(s) — most often a terminating value such as null, false, or [] so loops in the SUT stop calling.
  2. Replace willReturnOnConsecutiveCalls() with willReturnCallback() or returnValueMap() when call count is unpredictable: map arguments to returns, or generate values until a stop condition.
  3. Reduce the number of calls the SUT makes (fix the loop/retry bound in production code) if the extra call is itself the defect.
  4. 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.

Example fix

// before
$api->method('loadPage')
    ->willReturnOnConsecutiveCalls($page1, $page2);
// SUT loops until null -> third call throws NoMoreReturnValuesConfiguredException

// after
$api->method('loadPage')
    ->willReturnOnConsecutiveCalls($page1, $page2, null); // terminator stops the loop
Defensive patterns

Strategy: validation

Validate before calling

// Guard before the SUT runs when call count is data-driven:
$iterations = count($items) + 1; // how many times fetch() will be called
$values     = array_map(fn ($i) => $pages[$i] ?? null, range(0, $iterations - 1));
$api->method('loadPage')->willReturnOnConsecutiveCalls(...$values);

Try / catch

use PHPUnit\Framework\MockObject\NoMoreReturnValuesConfiguredException;

try {
    $result = $sut->drain();
} catch (NoMoreReturnValuesConfiguredException $e) {
    self::fail('Stub ran out of values: ' . $e->getMessage());
}

Prevention

When it happens

Trigger: $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.

Common situations: 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.

Related errors


AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23). Data as JSON: /api/errors/40e0a42afafea7bf. Report an issue: GitHub.