sebastianbergmann/phpunit · error · CannotUseOnlyMethodsException

Trying to configure method "%s" with onlyMethods(), but it d

Error message

Trying to configure method "%s" with onlyMethods(), but it does not exist in class "%s"

What it means

onlyMethods() deliberately restricts a mock to a subset of methods that must already exist on the targeted class — it cannot invent new ones. For every name passed, PHPUnit reflects the class and checks hasMethod(); the first name with no real method (including inherited ones) triggers CannotUseOnlyMethodsException with the message 'Trying to configure method "%s" with onlyMethods(), but it does not exist in class "%s"'. This separates typos/stale names from legitimate additions and points you to the right API.

Source

Thrown at src/Framework/MockObject/TestDoubleBuilder.php:89

            return $this;
        }

        try {
            $reflector = new ReflectionClass($this->type);

            // @codeCoverageIgnoreStart
        } catch (\ReflectionException $e) {
            throw new ReflectionException(
                $e->getMessage(),
                $e->getCode(),
                $e,
            );
            // @codeCoverageIgnoreEnd
        }

        foreach ($methods as $method) {
            if (!$reflector->hasMethod($method)) {
                throw new CannotUseOnlyMethodsException($this->type, $method);
            }
        }

        $this->methods = array_merge($this->methods, $methods);

        return $this;
    }

    /**
     * Specifies properties that do not declare property hooks for which property hooks should be doubled.
     *
     * @param list<non-empty-string> $properties
     *
     * @throws PropertyCannotBeDoubledException
     * @throws ReflectionException
     *
     * @return $this
     */

View on GitHub (pinned to f123cdb2a2)

Solutions

  1. Check the targeted class's actual API (Reflection or IDE) and correct the method name in onlyMethods().
  2. If you intentionally need a method the class does not declare (e.g. __call-based magic), use addMethods() instead of onlyMethods() — that is its purpose.
  3. If the method disappeared in an upgraded dependency, update the test to the new API or remove it from the list.
  4. If the list was copied from another test/class, trim it to methods of the class actually being mocked.

Example fix

// before
$mock = $this->createPartialMock(QueryBuilder::class, ['excecute']); // typo

// after
$mock = $this->createPartialMock(QueryBuilder::class, ['execute']);

// magic method that is not really declared -> use addMethods()
$mock = $this->getMockBuilder(SomeFacade::class)
             ->addMethods(['magicUndeclaredMethod'])
             ->getMock();
Defensive patterns

Strategy: validation

Validate before calling

// Check names against reflection before calling onlyMethods():
$methods = ['execute', 'fetchAll'];
$reflector = new ReflectionClass(QueryBuilder::class);
foreach ($methods as $m) {
    if (!$reflector->hasMethod($m)) {
        self::fail("QueryBuilder has no method '{$m}' — typo, or needs addMethods()?");
    }
}
$mock = $this->createPartialMock(QueryBuilder::class, $methods);

Type guard

// Typed helper guaranteeing declared methods only:
/** @param list<non-empty-string> $methods */
function onlyDeclaredMethods(string $class, array $methods): array
{
    $r = new ReflectionClass($class);
    return array_filter($methods, static fn (string $m): bool => $r->hasMethod($m));
}

Try / catch

use PHPUnit\Framework\MockObject\CannotUseOnlyMethodsException;

try {
    $builder->onlyMethods($names);
} catch (CannotUseOnlyMethodsException $e) {
    // $e names the class and the first offending method; fall back to addMethods()
    $builder->addMethods(array_diff($names, get_class_methods($this->type)));
}

Prevention

When it happens

Trigger: $this->createPartialMock(UserRepository::class, ['findyBy']) (typo), or mocking a method that exists only as @method annotation / __call magic: ReflectionClass::hasMethod('magicName') returns false and CannotUseOnlyMethodsException is thrown from the loop at TestDoubleBuilder.php:87-91. Also triggered after the real method was renamed/removed upstream but the test still lists the old name.

Common situations: Typos or letter-case mismatches (PHP method names are case-insensitive at call time, but hasMethod is case-insensitive too — real culprits are renames, removals, and magic methods); asserting against docblock @method magic methods of a facade; upgrading a dependency whose API dropped/renamed the method; copying an onlyMethods() list between classes; mocking methods of the wrong class because ::class resolved to a parent/interface.

Related errors


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