{"record":{"id":"a3985f4e541ba67d","repo":"sebastianbergmann/phpunit","slug":"expected-invocation-at-least-d-time-s-but-it-occu","errorCode":null,"errorMessage":"Expected invocation at least %d time%s but it occurred %d time%s.","messagePattern":"Expected invocation at least (.+?) time(.+?) but it occurred (.+?) time(.+?)\\.","errorType":"exception","errorClass":"ExpectationFailedException","httpStatus":null,"severity":"error","filePath":"src/Framework/MockObject/Runtime/Rule/InvokedAtLeastCount.php","lineNumber":50,"sourceCode":"        return sprintf(\n            'invoked at least %d time%s',\n            $this->requiredInvocations,\n            $this->requiredInvocations !== 1 ? 's' : '',\n        );\n    }\n\n    /**\n     * Verifies that the current expectation is valid. If everything is OK the\n     * code should just return, if not it must throw an exception.\n     *\n     * @throws ExpectationFailedException\n     */\n    public function verify(): void\n    {\n        $actualInvocations = $this->numberOfInvocations();\n\n        if ($actualInvocations < $this->requiredInvocations) {\n            throw new ExpectationFailedException(\n                sprintf(\n                    'Expected invocation at least %d time%s but it occurred %d time%s.',\n                    $this->requiredInvocations,\n                    $this->requiredInvocations !== 1 ? 's' : '',\n                    $actualInvocations,\n                    $actualInvocations !== 1 ? 's' : '',\n                ),\n            );\n        }\n    }\n\n    public function matches(BaseInvocation $invocation): bool\n    {\n        return true;\n    }\n}\n","sourceCodeStart":32,"sourceCodeEnd":67,"githubUrl":"https://github.com/sebastianbergmann/phpunit/blob/f123cdb2a2d49f15025794166cfed8bda8627dd2/src/Framework/MockObject/Runtime/Rule/InvokedAtLeastCount.php#L32-L67","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Check the test failure output for the actual invocation count and inspect the code under test to see which path skipped the call.","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.","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.","For exact numbers use $this->exactly(N) instead of atLeast(N) so a too-low count fails with an unambiguous message.","If the call is genuinely optional, switch to a spy-style approach or expects($this->any()) and verify behavior separately."],"exampleFix":"// before\n$logger->expects($this->atLeast(2))->method('info');\n$service->doWork('a'); // only triggers one $logger->info() call\n\n// after: exercise the code path that calls it again, or relax the lower bound\n$logger->expects($this->atLeast(2))->method('info');\n$service->doWork('a');\n$service->doWork('b');","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefer atLeast(1)/atLeastOnce() over atLeast(N) unless the minimum count is a real contract.","Drive tests with deterministic fixtures so the number of interactions is predictable.","Assert observable outcomes (return values, state) instead of interaction counts where possible.","Run the full test after refactoring call paths in the SUT so count expectations are re-validated."],"tags":["phpunit","mock","verification","invocation-count"],"backgroundTag":"mock-invocation-count-mismatch","analyzedSha":"f123cdb2a2d49f15025794166cfed8bda8627dd2","analyzedAt":"2026-08-23T01:20:58.058Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}