{"record":{"id":"b3d9ca8165aded32","repo":"sebastianbergmann/phpunit","slug":"method-was-expected-to-be-called-d-time-s-actual","errorCode":null,"errorMessage":"Method was expected to be called %d time%s, actually called %d time%s.","messagePattern":"Method was expected to be called (.+?) time(.+?), actually called (.+?) time(.+?)\\.","errorType":"exception","errorClass":"ExpectationFailedException","httpStatus":null,"severity":"error","filePath":"src/Framework/MockObject/Runtime/Rule/InvokedCount.php","lineNumber":63,"sourceCode":"    }\n\n    public function matches(BaseInvocation $invocation): bool\n    {\n        return true;\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        $actualCount = $this->numberOfInvocations();\n\n        if ($actualCount !== $this->expectedCount) {\n            throw new ExpectationFailedException(\n                sprintf(\n                    'Method was expected to be called %d time%s, actually called %d time%s.',\n                    $this->expectedCount,\n                    $this->expectedCount !== 1 ? 's' : '',\n                    $actualCount,\n                    $actualCount !== 1 ? 's' : '',\n                ),\n            );\n        }\n    }\n\n    /**\n     * @throws ExpectationFailedException\n     */\n    protected function invokedDo(BaseInvocation $invocation): void\n    {\n        $count = $this->numberOfInvocations();\n","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/sebastianbergmann/phpunit/blob/f123cdb2a2d49f15025794166cfed8bda8627dd2/src/Framework/MockObject/Runtime/Rule/InvokedCount.php#L45-L81","documentation":"PHPUnit throws this at mock-verification time when a method stubbed with expects($this->exactly(N)) (or once(), twice(), times(N), never()) was called a different number of times than expected. InvokedCount::verify() runs after the test body and requires the recorded count to equal the expected count exactly — both too few and too many calls fail here (if the test survived that long; see the invokedDo error for the mid-test variant).","triggerScenarios":"$mock->expects($this->exactly(2))->method('log') while the code calls log() once or three times; once() combined with code that calls the method zero or two times; never() where the method ends up being called at least once.","commonSituations":"Conditional logic (caching, feature flags, short-circuit evaluation) changing how many times a collaborator is hit; loops whose iteration count depends on fixtures; refactors that hoist or duplicate a call; once() used as a lazy 'it gets called' assertion that breaks when a second legitimate call is added; concurrent/recursive code paths multiplying invocations.","solutions":["Read the expected vs actually-called counts in the message and decide which one reflects the real contract.","If the actual count is correct, update the expectation: exactly(3) instead of exactly(2), or switch to atLeast()/atMost() when the exact count is not part of the contract.","If the expected count is correct, debug why the code under test calls the method a different number of times (extra loop iteration, missing return, double dispatch).","For zero-times assertions use never() — but note that even one invocation fails verification with this message context.","When you only care that the method ran, prefer expects($this->atLeastOnce()) to make the test less brittle."],"exampleFix":"// before\n$cache->expects($this->exactly(2))->method('get');\n$service->lookup($id); // first call warms the cache, so get() runs only once\n\n// after: assert the count the code really produces, or loosen it\n$cache->expects($this->atLeast(1))->method('get');","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use exactly(N) only with deterministic inputs; derive N from the fixture when possible.","Prefer atLeastOnce() when the precise count is incidental to the behavior under test.","Keep stubbed interactions minimal: each expects() you add is a contract the SUT must satisfy.","Re-run the suite after changing loop bounds, caching, or conditional call paths."],"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"}