{"record":{"id":"5deb4fa3153a9b62","repo":"sebastianbergmann/comparator","slug":"s-is-not-instance-of-expected-class-s","errorCode":null,"errorMessage":"%s is not instance of expected class \"%s\".","messagePattern":"(.+?) is not instance of expected class \"(.+?)\"\\.","errorType":"exception","errorClass":"ComparisonFailure","httpStatus":null,"severity":"error","filePath":"src/ObjectComparator.php","lineNumber":51,"sourceCode":"    {\n        return is_object($expected) && is_object($actual);\n    }\n\n    /**\n     * @param array<mixed> $processed\n     *\n     * @throws ComparisonFailure\n     * @throws ObjectNotSupportedException\n     */\n    public function assertEquals(mixed $expected, mixed $actual, float $delta = 0.0, bool $canonicalize = false, bool $ignoreCase = false, array &$processed = []): void\n    {\n        assert(is_object($expected));\n        assert(is_object($actual));\n\n        if ($actual::class !== $expected::class) {\n            $exporter = $this->exporter();\n\n            throw new ComparisonFailure(\n                $expected,\n                $actual,\n                $exporter->export($expected),\n                $exporter->export($actual),\n                sprintf(\n                    '%s is not instance of expected class \"%s\".',\n                    $exporter->export($actual),\n                    $expected::class,\n                ),\n                $this->contextLines(),\n            );\n        }\n\n        // don't compare twice to allow for cyclic dependencies\n        if (in_array([$actual, $expected], $processed, true) ||\n            in_array([$expected, $actual], $processed, true)) {\n            return;\n        }","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/sebastianbergmann/comparator/blob/00837a9d227b4e123b0b42a91736960f6b0b3583/src/ObjectComparator.php#L33-L69","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\n$this->assertEquals(new DateTime('2024-01-01'), $dto->date); // DateTimeImmutable vs DateTime\n// after\n$this->assertEquals(new DateTimeImmutable('2024-01-01'), $dto->date);","handlingStrategy":"type-guard","validationCode":"if (get_class($actual) !== get_class($expected)) {\n    throw new InvalidArgumentException(sprintf('Expected %s, got %s', get_class($expected), get_class($actual)));\n}","typeGuard":"/** @phpstan-assert ExpectedClass $v */\nfunction isExpectedClass(mixed $v): bool { return $v instanceof ExpectedClass && $v::class === ExpectedClass::class; }","tryCatchPattern":"try {\n    $comparator->assertEquals($expectedObject, $actualObject);\n} catch (ComparisonFailure $e) {\n    // classes differ: fall back to property-level comparison or normalize types\n}","preventionTips":["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"],"tags":["php","phpunit","objects","type-mismatch","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"}