{"record":{"id":"cc683cc5eac39c3a","repo":"sebastianbergmann/phpunit","slug":"comparison-method-s-s-does-not-exist","errorCode":null,"errorMessage":"Comparison method %s::%s() does not exist.","messagePattern":"Comparison method (.+?)::(.+?)\\(\\) does not exist\\.","errorType":"exception","errorClass":"ComparisonMethodDoesNotExistException","httpStatus":null,"severity":"error","filePath":"src/Framework/Constraint/Object/ObjectEquals.php","lineNumber":75,"sourceCode":"\n    /**\n     * @throws ActualValueIsNotAnObjectException\n     * @throws ComparisonMethodDoesNotAcceptParameterTypeException\n     * @throws ComparisonMethodDoesNotDeclareBoolReturnTypeException\n     * @throws ComparisonMethodDoesNotDeclareExactlyOneParameterException\n     * @throws ComparisonMethodDoesNotDeclareParameterTypeException\n     * @throws ComparisonMethodDoesNotExistException\n     */\n    protected function matches(mixed $other): bool\n    {\n        if (!is_object($other)) {\n            throw new ActualValueIsNotAnObjectException;\n        }\n\n        $object = new ReflectionObject($other);\n\n        if (!$object->hasMethod($this->method)) {\n            throw new ComparisonMethodDoesNotExistException(\n                $other::class,\n                $this->method,\n            );\n        }\n\n        $method = $object->getMethod($this->method);\n\n        if (!$method->hasReturnType()) {\n            throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(\n                $other::class,\n                $this->method,\n            );\n        }\n\n        $returnType = $method->getReturnType();\n\n        if (!$returnType instanceof ReflectionNamedType) {\n            throw new ComparisonMethodDoesNotDeclareBoolReturnTypeException(","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/sebastianbergmann/phpunit/blob/f123cdb2a2d49f15025794166cfed8bda8627dd2/src/Framework/Constraint/Object/ObjectEquals.php#L57-L93","documentation":"PHPUnit's assertObjectEquals() does not compare objects itself; it delegates to a user-defined comparison method on the actual object (default name 'equals'). Before calling it, the ObjectEquals constraint reflects on the actual object's class and requires the configured method to exist. If ReflectionObject::hasMethod() fails, this exception is thrown, meaning the actual value's class has no equals() (or custom-named) method.","triggerScenarios":"assertObjectEquals($expected, $actual) where $actual's class declares no public equals() method; or assertObjectEquals($expected, $actual, 'isEqualTo') where 'isEqualTo' does not exist on the actual class (typo, or method only exists on the expected class, or it is defined on a parent the actual object does not extend).","commonSituations":"Value objects that only implement a static equals() or an isEqualTo() with a different name; test authors assuming assertObjectEquals works like assertEquals and skipping the equals() method; the method exists but on the expected object's class instead of the actual one; PHP 8 constructor-promoted DTOs where equals() was never written.","solutions":["Add a public instance method named equals (or the name passed as 3rd argument) to the actual object's class: `public function equals(self $other): bool`","If your method has a different name (e.g. isEqualTo), pass it explicitly: assertObjectEquals($expected, $actual, 'isEqualTo')","If you cannot modify the class, fall back to assertEquals($expected, $actual) which compares structurally without a custom method"],"exampleFix":"// before\n$this->assertObjectEquals($expectedMoney, $actualMoney);\n// RuntimeException-ish failure: Comparison method Money::equals() does not exist.\n\nclass Money { /* no equals() */ }\n\n// after\nclass Money\n{\n    public function equals(self $other): bool\n    {\n        return $this->amount === $other->amount && $this->currency === $other->currency;\n    }\n}\n$this->assertObjectEquals($expectedMoney, $actualMoney);","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"function hasComparisonMethod(object $actual, string $method = 'equals'): bool\n{\n    return (new ReflectionObject($actual))->hasMethod($method);\n}\n\nif (!hasComparisonMethod($actual, 'equals')) {\n    $this->markTestIncomplete(get_class($actual) . ' has no equals() method');\n}","tryCatchPattern":"use PHPUnit\\Framework\\ComparisonMethodDoesNotExistException;\n\ntry {\n    $this->assertObjectEquals($expected, $actual);\n} catch (ComparisonMethodDoesNotExistException $e) {\n    // authoring error: fix the class, do not swallow\n    $this->fail('Comparison method missing: ' . $e->getMessage());\n}","preventionTips":["Pair assertObjectEquals() with a dedicated self-typed equals(): `public function equals(self $other): bool`","Add a static-analysis/phpstan rule or architecture test that value objects used with assertObjectEquals declare equals()"],"tags":["php","phpunit","assertobjectequals","equals-method","reflection"],"backgroundTag":"equals-method-contract-violation","analyzedSha":"f123cdb2a2d49f15025794166cfed8bda8627dd2","analyzedAt":"2026-08-23T01:20:58.058Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}