sebastianbergmann/comparator · error · ComparisonFailure
is not instance of expected class " ".
Error message
%s is not instance of expected class "%s".
What it means
ObjectComparator, the base comparator for objects in sebastian/comparator, throws a ComparisonFailure when the actual object's class differs from the expected object's class. Subclass comparators (DateTime, SplObjectStorage, etc.) only accept same-class pairs, so cross-class comparisons land here and fail immediately.
Solutions
- Check the message and diff for the actual class name and fix the code to construct the expected class
- Update the expected object/class in the test if the new type is intended
- Normalize before comparing: cast/copy the value into the expected class or compare individual properties instead of whole objects
- If polymorphic equality is intended, register a custom comparator or compare with assertInstanceOf followed by property-level assertions
Example fix
// before
$this->assertEquals(new DateTime('2024-01-01'), $dto->date); // DateTimeImmutable vs DateTime
// after
$this->assertEquals(new DateTimeImmutable('2024-01-01'), $dto->date); Defensive patterns
Strategy: type-guard
Validate before calling
if (get_class($actual) !== get_class($expected)) {
throw new InvalidArgumentException(sprintf('Expected %s, got %s', get_class($expected), get_class($actual)));
} Type guard
/** @phpstan-assert ExpectedClass $v */
function isExpectedClass(mixed $v): bool { return $v instanceof ExpectedClass && $v::class === ExpectedClass::class; } Try / catch
try {
$comparator->assertEquals($expectedObject, $actualObject);
} catch (ComparisonFailure $e) {
// classes differ: fall back to property-level comparison or normalize types
} Prevention
- Assert the class first with assertInstanceOf before comparing object contents
- Be explicit about final classes vs subclasses when refactoring DTOs used in tests
- Normalize value objects (e.g. convert DateTimeImmutable to DateTime) at boundaries instead of comparing mixed types
- Don't rely on duck typing: two objects with equal properties of different classes are not equal to this comparator
When it happens
Trigger: assertEquals($expectedObject, $actualObject) where $actual::class !== $expected::class, e.g. comparing a ParentClass instance to a ChildClass instance, or two different classes that happen to look similar (DateTime vs DateTimeImmutable, stdClass vs a DTO).
Common situations: Tests after a refactor replaced a DTO with a subclass or a differently-named class; mocking where a mock's class (e.g. created by PHPUnit's mock engine) differs from the real class; comparing DateTime with DateTimeImmutable; deserializers returning stdClass instead of the typed class.
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 values of enumeration
- Failed asserting that two Number objects are equal.
- Failed asserting that
- Failed asserting that two arrays are equal.
- Failed asserting that closure declared at
AI-assisted analysis of sebastianbergmann/comparator@00837a9d22 (2026-09-15).
Data as JSON: /api/errors/5deb4fa3153a9b62.
Report an issue: GitHub.
Appendix: source
Thrown at src/ObjectComparator.php:51
{
return is_object($expected) && is_object($actual);
}
/**
* @param array<mixed> $processed
*
* @throws ComparisonFailure
* @throws ObjectNotSupportedException
*/
public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false, array &$processed = []): void
{
assert(is_object($expected));
assert(is_object($actual));
if ($actual::class !== $expected::class) {
$exporter = $this->exporter();
throw new ComparisonFailure(
$expected,
$actual,
$exporter->export($expected),
$exporter->export($actual),
sprintf(
'%s is not instance of expected class "%s".',
$exporter->export($actual),
$expected::class,
),
$this->contextLines(),
);
}
// don't compare twice to allow for cyclic dependencies
if (in_array([$actual, $expected], $processed, true) ||
in_array([$expected, $actual], $processed, true)) {
return;
}View on GitHub (pinned to 00837a9d22)