sebastianbergmann/comparator · error · ComparisonFailure
Failed asserting that closure declared at
Error message
Failed asserting that closure declared at %s:%d is equal to closure declared at %s:%d.
What it means
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.
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.
Example fix
// before $c = new Factory; $c->getComparatorFor($f, $g)->assertEquals($f, $g); // always throws for distinct closures // after $this->assertSame($f, $f); // identity, or: assertEquals($f($in), $g($in)); // compare behavior
Defensive patterns
Strategy: type-guard
Validate before calling
// only compare when identity equality is impossible/irrelevant
if ($expected === $actual) { /* equal, skip comparator */ } Type guard
function isClosure(mixed $v): bool { return $v instanceof Closure; } Try / catch
try {
$factory->getComparatorFor($f, $g)->assertEquals($f, $g);
} catch (ComparisonFailure $e) {
// closures are never equal unless identical; parse declaration location from message
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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).
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Failed asserting that two arrays are equal.
- Failed asserting that two DOM
- Failed asserting that two DateInterval objects are equal.
- Failed asserting that two DateTime objects are equal.
- Failed asserting that two values of enumeration
AI-assisted analysis of sebastianbergmann/comparator@00837a9d22 (2026-09-15).
Data as JSON: /api/errors/85f80fcbe1713061.
Report an issue: GitHub.
Appendix: source
Thrown at src/ClosureComparator.php:69
$actualReflector = new ReflectionFunction($actual);
if ($this->declarationIsEqual($expectedReflector, $actualReflector) &&
$this->bindingIsEqual($expectedReflector, $actualReflector) &&
$this->capturedStateIsEqual($expectedReflector, $actualReflector)) {
return;
}
$expectedFilename = $expectedReflector->getFileName();
$expectedStartLine = $expectedReflector->getStartLine();
$actualFilename = $actualReflector->getFileName();
$actualStartLine = $actualReflector->getStartLine();
assert($expectedFilename !== false);
assert($expectedStartLine !== false);
assert($actualFilename !== false);
assert($actualStartLine !== false);
throw new ComparisonFailure(
$expected,
$actual,
'Closure Object #' . spl_object_id($expected) . ' ()',
'Closure Object #' . spl_object_id($actual) . ' ()',
sprintf(
'Failed asserting that closure declared at %s:%d is equal to closure declared at %s:%d.',
$expectedFilename,
$expectedStartLine,
$actualFilename,
$actualStartLine,
),
$this->contextLines(),
);
}
private function declarationIsEqual(ReflectionFunction $expected, ReflectionFunction $actual): bool
{
return $expected->getName() === $actual->getName() &&View on GitHub (pinned to 00837a9d22)