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

  1. Treat it as 'expected method was never called': verify the code under test actually reaches the mocked call.
  2. Confirm the mock instance is the one injected into the SUT and the method() name is spelled correctly.
  3. If the call should not happen, remove the with() constraint (and the whole expectation) — asserting arguments of a call that never occurs is contradictory.
  4. If the call should happen, fix the SUT path (guard clause, exception, wiring) that prevents it.
  5. 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

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


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