{"record":{"id":"b78d45e374cb52e6","repo":"sebastianbergmann/phpunit","slug":"trying-to-configure-method-s-with-onlymethods","errorCode":null,"errorMessage":"Trying to configure method \"%s\" with onlyMethods(), but it does not exist in class \"%s\"","messagePattern":"Trying to configure method \"(.+?)\" with onlyMethods\\(\\), but it does not exist in class \"(.+?)\"","errorType":"exception","errorClass":"CannotUseOnlyMethodsException","httpStatus":null,"severity":"error","filePath":"src/Framework/MockObject/TestDoubleBuilder.php","lineNumber":89,"sourceCode":"            return $this;\n        }\n\n        try {\n            $reflector = new ReflectionClass($this->type);\n\n            // @codeCoverageIgnoreStart\n        } catch (\\ReflectionException $e) {\n            throw new ReflectionException(\n                $e->getMessage(),\n                $e->getCode(),\n                $e,\n            );\n            // @codeCoverageIgnoreEnd\n        }\n\n        foreach ($methods as $method) {\n            if (!$reflector->hasMethod($method)) {\n                throw new CannotUseOnlyMethodsException($this->type, $method);\n            }\n        }\n\n        $this->methods = array_merge($this->methods, $methods);\n\n        return $this;\n    }\n\n    /**\n     * Specifies properties that do not declare property hooks for which property hooks should be doubled.\n     *\n     * @param list<non-empty-string> $properties\n     *\n     * @throws PropertyCannotBeDoubledException\n     * @throws ReflectionException\n     *\n     * @return $this\n     */","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/sebastianbergmann/phpunit/blob/f123cdb2a2d49f15025794166cfed8bda8627dd2/src/Framework/MockObject/TestDoubleBuilder.php#L71-L107","documentation":"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.","triggerScenarios":"$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.","commonSituations":"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.","solutions":["Check the targeted class's actual API (Reflection or IDE) and correct the method name in onlyMethods().","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.","If the method disappeared in an upgraded dependency, update the test to the new API or remove it from the list.","If the list was copied from another test/class, trim it to methods of the class actually being mocked."],"exampleFix":"// before\n$mock = $this->createPartialMock(QueryBuilder::class, ['excecute']); // typo\n\n// after\n$mock = $this->createPartialMock(QueryBuilder::class, ['execute']);\n\n// magic method that is not really declared -> use addMethods()\n$mock = $this->getMockBuilder(SomeFacade::class)\n             ->addMethods(['magicUndeclaredMethod'])\n             ->getMock();","handlingStrategy":"validation","validationCode":"// Check names against reflection before calling onlyMethods():\n$methods = ['execute', 'fetchAll'];\n$reflector = new ReflectionClass(QueryBuilder::class);\nforeach ($methods as $m) {\n    if (!$reflector->hasMethod($m)) {\n        self::fail(\"QueryBuilder has no method '{$m}' — typo, or needs addMethods()?\");\n    }\n}\n$mock = $this->createPartialMock(QueryBuilder::class, $methods);","typeGuard":"// Typed helper guaranteeing declared methods only:\n/** @param list<non-empty-string> $methods */\nfunction onlyDeclaredMethods(string $class, array $methods): array\n{\n    $r = new ReflectionClass($class);\n    return array_filter($methods, static fn (string $m): bool => $r->hasMethod($m));\n}","tryCatchPattern":"use PHPUnit\\Framework\\MockObject\\CannotUseOnlyMethodsException;\n\ntry {\n    $builder->onlyMethods($names);\n} catch (CannotUseOnlyMethodsException $e) {\n    // $e names the class and the first offending method; fall back to addMethods()\n    $builder->addMethods(array_diff($names, get_class_methods($this->type)));\n}","preventionTips":["Use ::class + IDE completion for method names; never hand-type them from memory.","For @method-annotated magic methods, use addMethods() — onlyMethods() is strictly for declared methods.","After upgrading a dependency, run the mock-heavy suites first to catch removed/renamed methods.","Keep onlyMethods() lists short and next to the class they target so drift is visible."],"tags":["phpunit","mockobject","onlymethods","unknown-method","reflection"],"backgroundTag":"mocking-undeclared-method","analyzedSha":"f123cdb2a2d49f15025794166cfed8bda8627dd2","analyzedAt":"2026-08-23T01:20:58.058Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}