{"record":{"id":"85f80fcbe1713061","repo":"sebastianbergmann/comparator","slug":"failed-asserting-that-closure-declared-at-s-d-is-equal-to","errorCode":null,"errorMessage":"Failed asserting that closure declared at %s:%d is equal to closure declared at %s:%d.","messagePattern":"Failed asserting that closure declared at (.+?):(.+?) is equal to closure declared at (.+?):(.+?)\\.","errorType":"exception","errorClass":"ComparisonFailure","httpStatus":null,"severity":"error","filePath":"src/ClosureComparator.php","lineNumber":69,"sourceCode":"        $actualReflector   = new ReflectionFunction($actual);\n\n        if ($this->declarationIsEqual($expectedReflector, $actualReflector) &&\n            $this->bindingIsEqual($expectedReflector, $actualReflector) &&\n            $this->capturedStateIsEqual($expectedReflector, $actualReflector)) {\n            return;\n        }\n\n        $expectedFilename  = $expectedReflector->getFileName();\n        $expectedStartLine = $expectedReflector->getStartLine();\n        $actualFilename    = $actualReflector->getFileName();\n        $actualStartLine   = $actualReflector->getStartLine();\n\n        assert($expectedFilename !== false);\n        assert($expectedStartLine !== false);\n        assert($actualFilename !== false);\n        assert($actualStartLine !== false);\n\n        throw new ComparisonFailure(\n            $expected,\n            $actual,\n            'Closure Object #' . spl_object_id($expected) . ' ()',\n            'Closure Object #' . spl_object_id($actual) . ' ()',\n            sprintf(\n                'Failed asserting that closure declared at %s:%d is equal to closure declared at %s:%d.',\n                $expectedFilename,\n                $expectedStartLine,\n                $actualFilename,\n                $actualStartLine,\n            ),\n            $this->contextLines(),\n        );\n    }\n\n    private function declarationIsEqual(ReflectionFunction $expected, ReflectionFunction $actual): bool\n    {\n        return $expected->getName() === $actual->getName() &&","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/sebastianbergmann/comparator/blob/00837a9d227b4e123b0b42a91736960f6b0b3583/src/ClosureComparator.php#L51-L87","documentation":"ComparisonFailure thrown by ClosureComparator::assertEquals whenever two closures are compared. Closures are considered equal only if they are the same object; the message reports the file:line where each closure was declared. The exported strings are opaque 'Closure Object #id ()' placeholders.","triggerScenarios":"Any assertEquals($expectedClosure, $actualClosure) invocation through the comparator Factory where the two closures are distinct objects — even if the bodies are identical source code, they differ because spl_object_id / declaration location differ.","commonSituations":"PHPUnit tests asserting a callback was equal to an expected closure; passing anonymous functions through config and comparing them; identity checks of closures captured from different call sites (each literal creates a new Closure object, so identical code at two lines never matches).","solutions":["Compare closure identity with === (same instance) before asserting equality, or assert against the very same closure instance.","Instead of comparing closures, assert observable behavior: invoke both with sample arguments and compare return values.","If you control production code, inject a named invokable class or string callable instead of two separate closure literals.","Catch ComparisonFailure and inspect the declaration file:line in the message when the mismatch itself is the subject of the test."],"exampleFix":"// before\n$c = new Factory; $c->getComparatorFor($f, $g)->assertEquals($f, $g); // always throws for distinct closures\n// after\n$this->assertSame($f, $f); // identity, or: assertEquals($f($in), $g($in)); // compare behavior","handlingStrategy":"type-guard","validationCode":"// only compare when identity equality is impossible/irrelevant\nif ($expected === $actual) { /* equal, skip comparator */ }","typeGuard":"function isClosure(mixed $v): bool { return $v instanceof Closure; }","tryCatchPattern":"try {\n    $factory->getComparatorFor($f, $g)->assertEquals($f, $g);\n} catch (ComparisonFailure $e) {\n    // closures are never equal unless identical; parse declaration location from message\n}","preventionTips":["Never assert equality between two distinct closure literals; they always differ.","Compare closure behavior (invoke and compare results) or identity (===) instead.","Prefer named callables or invokable value objects in APIs under test."],"tags":["phpunit","closures","comparison","assertion"],"backgroundTag":"type-mismatch","analyzedSha":"00837a9d227b4e123b0b42a91736960f6b0b3583","analyzedAt":"2026-09-15T03:36:55.154Z","contentChangedAt":"2026-09-15T03:36:55.154Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}