44
55namespace Studio \OpenApiContractTesting \Tests \Unit ;
66
7+ use const JSON_THROW_ON_ERROR ;
8+
79use InvalidArgumentException ;
10+ use PHPUnit \Framework \Attributes \DataProvider ;
811use PHPUnit \Framework \Attributes \Test ;
912use PHPUnit \Framework \TestCase ;
13+ use ReflectionMethod ;
1014use Studio \OpenApiContractTesting \OpenApiResponseValidator ;
1115use Studio \OpenApiContractTesting \OpenApiSpecLoader ;
1216
1317use function array_map ;
1418use function count ;
19+ use function json_encode ;
1520use function range ;
1621
1722class OpenApiResponseValidatorTest extends TestCase
@@ -32,6 +37,42 @@ protected function tearDown(): void
3237 parent ::tearDown ();
3338 }
3439
40+ // ========================================
41+ // toObject equivalence tests
42+ // ========================================
43+
44+ /**
45+ * @return iterable<string, array{mixed}>
46+ */
47+ public static function provideTo_object_matches_json_roundtripCases (): iterable
48+ {
49+ yield 'null ' => [null ];
50+ yield 'string ' => ['hello ' ];
51+ yield 'integer ' => [42 ];
52+ yield 'float ' => [3.14 ];
53+ yield 'boolean true ' => [true ];
54+ yield 'boolean false ' => [false ];
55+ yield 'empty array ' => [[]];
56+ yield 'sequential array ' => [[1 , 2 , 3 ]];
57+ yield 'associative array ' => [['key ' => 'value ' , 'num ' => 1 ]];
58+ yield 'nested associative ' => [['a ' => ['b ' => ['c ' => 'deep ' ]]]];
59+ yield 'list of objects ' => [[['id ' => 1 , 'name ' => 'a ' ], ['id ' => 2 , 'name ' => 'b ' ]]];
60+ yield 'non-sequential int keys ' => [[1 => 'a ' , 3 => 'b ' ]];
61+ yield 'mixed nested ' => [
62+ [
63+ 'users ' => [
64+ ['id ' => 1 , 'tags ' => ['admin ' , 'user ' ], 'meta ' => ['active ' => true ]],
65+ ],
66+ 'total ' => 1 ,
67+ 'filters ' => [],
68+ ],
69+ ];
70+ yield 'numeric string keys ' => [['200 ' => ['description ' => 'OK ' ]]];
71+ yield 'deeply nested list ' => [[[['a ' ]]]];
72+ yield 'null in array ' => [[null , 'a ' , null ]];
73+ yield 'empty nested object ' => [['data ' => []]];
74+ }
75+
3576 // ========================================
3677 // OAS 3.0 tests
3778 // ========================================
@@ -744,4 +785,20 @@ public function v30_strip_prefixes_applied(): void
744785 $ this ->assertTrue ($ result ->isValid ());
745786 $ this ->assertSame ('/v1/pets ' , $ result ->matchedPath ());
746787 }
788+
789+ #[Test]
790+ #[DataProvider('provideTo_object_matches_json_roundtripCases ' )]
791+ public function to_object_matches_json_roundtrip (mixed $ input ): void
792+ {
793+ $ method = new ReflectionMethod (OpenApiResponseValidator::class, 'toObject ' );
794+
795+ $ actual = $ method ->invoke (null , $ input );
796+
797+ // Re-encode both to JSON to compare structural equivalence
798+ // without relying on object identity (assertSame fails on stdClass).
799+ $ expectedJson = json_encode ($ input , JSON_THROW_ON_ERROR );
800+ $ actualJson = (string ) json_encode ($ actual , JSON_THROW_ON_ERROR );
801+
802+ $ this ->assertSame ($ expectedJson , $ actualJson );
803+ }
747804}
0 commit comments