{"record":{"id":"26713b5e16e5c064","repo":"sebastianbergmann/phpunit","slug":"s-was-not-expected-to-be-called-more-than-d-time","errorCode":null,"errorMessage":"%s was not expected to be called more than %d times, actually called %d time%s.","messagePattern":"(.+?) was not expected to be called more than (.+?) times, actually called (.+?) time(.+?)\\.","errorType":"exception","errorClass":"ExpectationFailedException","httpStatus":null,"severity":"error","filePath":"src/Framework/MockObject/Runtime/Rule/InvokedCount.php","lineNumber":96,"sourceCode":"    protected function invokedDo(BaseInvocation $invocation): void\n    {\n        $count = $this->numberOfInvocations();\n\n        if ($count > $this->expectedCount) {\n            $message = $invocation->toString() . ' ';\n\n            $message .= match ($this->expectedCount) {\n                0       => 'was not expected to be called',\n                1       => 'was not expected to be called more than once',\n                default => sprintf(\n                    'was not expected to be called more than %d times',\n                    $this->expectedCount,\n                ),\n            };\n\n            $message .= sprintf(', actually called %d time%s.', $count, $count !== 1 ? 's' : '');\n\n            throw new ExpectationFailedException($message);\n        }\n    }\n}\n","sourceCodeStart":78,"sourceCodeEnd":100,"githubUrl":"https://github.com/sebastianbergmann/phpunit/blob/f123cdb2a2d49f15025794166cfed8bda8627dd2/src/Framework/MockObject/Runtime/Rule/InvokedCount.php#L78-L100","documentation":"Unlike the verification-time variant, this PHPUnit error is thrown during the test itself, at the moment the (N+1)-th call hits the mock. InvokedCount::invokedDo() counts invocations as they happen and throws as soon as the count exceeds the configured exactly(N) / once() / never() limit. The message includes the concrete invocation (class::method with arguments) that went over budget, which pinpoints the offending call in your stack trace.","triggerScenarios":"$mock->expects($this->once())->method('connect') where the code under test calls connect() a second time; never() (expectedCount 0) where the method is called at all — the very first invocation throws 'was not expected to be called'; exactly(N) exceeded mid-loop. Because it throws at call time, the test aborts at the excess invocation rather than at mock verification.","commonSituations":"never() stubs hit by a hidden call such as a destructor, flush(), logger, or error handler; once() expectations broken when a fix adds a retry or a second iteration; recursive algorithms invoking the collaborator once per level; mocks created with createMock() whose methods return null and cause the SUT to retry; eager-loading or event systems dispatching more times than assumed.","solutions":["Use the included invocation string and stack trace to identify exactly which call exceeded the limit.","If the extra call is legitimate, raise the expectation (exactly(N+1)) or relax it to atMost(N) / any().","For never() failures, hunt the hidden call site — enable a xdebug backtrace or add a die()/log inside a willReturnCallback to see who calls it.","If the excess call reveals duplicate work in production code (double submit, missing idempotency guard), fix that code path.","If the count depends on data, make the fixture deterministic or assert against count($fixture) instead of a hardcoded number."],"exampleFix":"// before\n$db->expects($this->never())->method('rollback');\n$service->import($rows); // a failing row silently triggers rollback()\n\n// after: assert the rollback explicitly (or fix the import so it does not roll back)\n$db->expects($this->once())->method('rollback');","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// When diagnosing which call site breaks a never()/once() limit, let the exception\n// carry the trace out of the SUT and assert on it deliberately:\ntry {\n    $service->run();\n} catch (\\PHPUnit\\Framework\\ExpectationFailedException $e) {\n    self::fail('Invocation limit exceeded: ' . $e->getMessage());\n}","preventionTips":["never() is best combined with a backtrace capture: ->willReturnCallback(fn () => throw new LogicException('must not be called')) gives a clearer failure than the count message.","Keep loop-driven calls matched by deriving counts from the fixture.","Avoid strict once() expectations on methods that caching or short-circuits may skip or repeat.","Inspect the invocation string embedded in the message — it names the exact arguments of the offending call."],"tags":["phpunit","mock","invocation-count","runtime-failure"],"backgroundTag":"mock-invocation-count-mismatch","analyzedSha":"f123cdb2a2d49f15025794166cfed8bda8627dd2","analyzedAt":"2026-08-23T01:20:58.058Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}