sebastianbergmann/phpunit · error · ExpectationFailedException

Expected invocation at least %d time%s but it occurred %d ti

Error message

Expected invocation at least %d time%s but it occurred %d time%s.

What it means

This exception is thrown by PHPUnit's mock-object verification when a method stubbed with expects($this->atLeast(N)) was invoked fewer than N times. PHPUnit counts every real call made to the mocked method during the test and, when the mock is verified (after the test body runs), the InvokedAtLeastCount rule compares the actual count against the lower bound you configured. It is an assertion failure about interaction frequency, not about arguments or return values.

Source

Thrown at src/Framework/MockObject/Runtime/Rule/InvokedAtLeastCount.php:50

        return sprintf(
            'invoked at least %d time%s',
            $this->requiredInvocations,
            $this->requiredInvocations !== 1 ? 's' : '',
        );
    }

    /**
     * Verifies that the current expectation is valid. If everything is OK the
     * code should just return, if not it must throw an exception.
     *
     * @throws ExpectationFailedException
     */
    public function verify(): void
    {
        $actualInvocations = $this->numberOfInvocations();

        if ($actualInvocations < $this->requiredInvocations) {
            throw new ExpectationFailedException(
                sprintf(
                    'Expected invocation at least %d time%s but it occurred %d time%s.',
                    $this->requiredInvocations,
                    $this->requiredInvocations !== 1 ? 's' : '',
                    $actualInvocations,
                    $actualInvocations !== 1 ? 's' : '',
                ),
            );
        }
    }

    public function matches(BaseInvocation $invocation): bool
    {
        return true;
    }
}

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Check the test failure output for the actual invocation count and inspect the code under test to see which path skipped the call.
  2. If the code is correct, lower the expectation (e.g. $this->atLeast(1)) or drop it entirely and assert observable results instead of interaction counts.
  3. If the code is wrong (a call really must happen N times), fix the production code path the test exercises — often an early return or swallowed exception.
  4. For exact numbers use $this->exactly(N) instead of atLeast(N) so a too-low count fails with an unambiguous message.
  5. If the call is genuinely optional, switch to a spy-style approach or expects($this->any()) and verify behavior separately.

Example fix

// before
$logger->expects($this->atLeast(2))->method('info');
$service->doWork('a'); // only triggers one $logger->info() call

// after: exercise the code path that calls it again, or relax the lower bound
$logger->expects($this->atLeast(2))->method('info');
$service->doWork('a');
$service->doWork('b');
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: Configuring a mock with $mock->expects($this->atLeast(2))->method('foo') but the code under test only calling foo() zero or one times during the test. The exception surfaces when PHPUnit verifies the expectation, i.e. after the test method finishes (or inside an explicit $mock->__phpunit_verify() / assertion).

Common situations: Refactoring production code so an early return, guard clause, or exception path skips calls the test still requires; loops that iterate one fewer time than assumed; stubbing a collaborator whose call only happens on a branch the test does not exercise; counting retry/flush/commit calls that are conditional on success.

Related errors


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