sebastianbergmann/phpunit · error · ExpectationFailedException
Doubled method does not exist.
Error message
Doubled method does not exist.
What it means
A misleading PHPUnit message: the doubled method usually does exist — the real condition is that it was never invoked. Parameters::doVerify() throws 'Doubled method does not exist.' when it is asked to verify ->with() constraints but its recorded invocation is still null, which happens when the mocked method was not called at all during the test (apply() never ran). You typically see it when a with() expectation sits on a method the code under test never reaches.
Source
Thrown at src/Framework/MockObject/Runtime/Rule/Parameters.php:103
$this->doVerify();
}
public function useAssertionCount(bool $useAssertionCount): void
{
$this->useAssertionCount = $useAssertionCount;
}
/**
* @throws ExpectationFailedException
*/
private function doVerify(): bool
{
if (isset($this->parameterVerificationResult)) {
return $this->guardAgainstDuplicateEvaluationOfParameterConstraints();
}
if ($this->invocation === null) {
throw new ExpectationFailedException('Doubled method does not exist.');
}
$invocation = $this->invocation;
$invocationParameters = $invocation->parameters();
if (count($invocationParameters) < count($this->parameters)) {
$message = 'Parameter count for invocation %s is too low.';
// The user called `->with($this->anything())`, but may have meant
// `->withAnyParameters()`.
//
// @see https://github.com/sebastianbergmann/phpunit-mock-objects/issues/199
if (count($this->parameters) === 1 &&
$this->parameters[0]::class === IsAnything::class) {
$message .= "\nTo allow 0 or more parameters with any value, omit ->with() or use ->withAnyParameters() instead.";
}
$this->incrementAssertionCount();View on GitHub (pinned to f123cdb2a2)
Solutions
- Treat it as 'expected method was never called': verify the code under test actually reaches the mocked call.
- Confirm the mock instance is the one injected into the SUT and the method() name is spelled correctly.
- If the call should not happen, remove the with() constraint (and the whole expectation) — asserting arguments of a call that never occurs is contradictory.
- If the call should happen, fix the SUT path (guard clause, exception, wiring) that prevents it.
- Prefer expects($this->atLeastOnce()) over any() so a never-called method fails with a clear count message instead of this one.
Example fix
// before
$api->expects($this->any())->method('fetch')->with('/users');
$service->listUsers(); // uses a cached path, fetch() never called
// after: assert the interaction explicitly (or drop the stale expectation)
$api->expects($this->atLeastOnce())->method('fetch')->with('/users');
$service->listUsers(forceRefresh: true); Defensive patterns
Strategy: validation
Validate before calling
// Ensure the interaction happens before parameter verification matters:
// pair with() with atLeastOnce() so a never-called method fails with a clear
// count error rather than the misleading 'Doubled method does not exist.'
$mock->expects($this->atLeastOnce())->method('foo')->with(1); Prevention
- Never use expects(any()) with with() on a method that might not be called — verify it first with atLeastOnce().
- Treat this message as 'method never invoked', not as a mocking-generator problem; check wiring and call path first.
- Remove with() constraints when the expectation is removed; stale parameter rules fire at verification time.
- Assert the call path with a return-value assertion before asserting its arguments.
When it happens
Trigger: $mock->expects($this->any())->method('foo')->with($this->equalTo(1)) where foo() is never called; verification of a matcher with a Parameters rule but zero invocations; calling the expectation's verify path directly (e.g. $mock->__phpunit_verify() or a failed earlier expectation) before any invocation was recorded. Note expects($this->any()) does not itself verify the count, so the parameter rule's null-invocation check is what fails.
Common situations: any()-based expectations on methods that a refactor stopped calling; method name typo in method(); the mock not being injected into the SUT; an exception thrown earlier in the SUT skipping the call; tests asserting arguments for a call that only happens on an untested branch; PHPUnit 10+ where the message text is unchanged but users still grep for it thinking the mock generator failed.
Related errors
- Expected invocation at least once but it never occurred.
- Parameter count for invocation %s is too low.
- Expected invocation at least %d time%s but it occurred %d ti
- Expected invocation at most %d time%s but it occurred %d tim
- Method was expected to be called %d time%s, actually called
AI-assisted analysis of sebastianbergmann/phpunit@f123cdb2a2 (2026-08-23).
Data as JSON: /api/errors/a71454ee473ff201.
Report an issue: GitHub.