Skip to content

Commit f694087

Browse files
authored
test: cover Serializer with more tests (#700)
1 parent 215e6ff commit f694087

3 files changed

Lines changed: 116 additions & 14 deletions

File tree

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"phpunit --colors --testsuite=unit --do-not-cache-result"
7878
],
7979
"infection": [
80-
"./bin/infection --threads=8 --only-covering-test-cases --min-msi=99 --show-mutations"
80+
"./bin/infection --threads=8 --only-covering-test-cases --min-msi=100 --min-covered-msi=100 --show-mutations"
8181
],
8282
"tests:unit": [
8383
"@phpunit:unit",

infection.json.dist

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,6 @@
66
]
77
},
88
"mutators": {
9-
"@default": true,
10-
"IncrementInteger": {
11-
"ignoreSourceCodeByRegex": [
12-
".*json_decode.*"
13-
]
14-
},
15-
"DecrementInteger": {
16-
"ignoreSourceCodeByRegex": [
17-
".*json_decode.*"
18-
]
19-
}
9+
"@default": true
2010
}
2111
}

tests/unit/SerializerTest.php

Lines changed: 114 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
namespace Tests\Lendable\Json\Unit;
66

77
use PHPUnit\Framework\Attributes\CoversClass;
8+
use PHPUnit\Framework\Attributes\DataProvider;
89
use PHPUnit\Framework\Attributes\Test;
910
use Lendable\Json\DeserializationFailed;
11+
use Lendable\Json\Failure;
1012
use Lendable\Json\InvalidDeserializedData;
1113
use Lendable\Json\SerializationFailed;
1214
use Lendable\Json\Serializer;
@@ -18,6 +20,8 @@
1820
#[CoversClass(InvalidDeserializedData::class)]
1921
final class SerializerTest extends TestCase
2022
{
23+
private const int MAX_NESTING_DEPTH = 511;
24+
2125
private Serializer $serializer;
2226

2327
#[Test]
@@ -28,6 +32,12 @@ public function it_can_serialize_an_array_of_scalars_to_json(): void
2832
$this->assertSame('{"foo":"bar","baz":[1.03,true,"foobar"],"emoji":"😃","with_forward_slash":"foo/bar"}', $result);
2933
}
3034

35+
#[Test]
36+
public function it_can_serialize_an_empty_array_to_json(): void
37+
{
38+
$this->assertSame('[]', $this->serializer->serialize([]));
39+
}
40+
3141
#[Test]
3242
public function it_throws_when_serializing_if_an_error_encountered(): void
3343
{
@@ -37,6 +47,29 @@ public function it_throws_when_serializing_if_an_error_encountered(): void
3747
$this->serializer->serialize(["\xf0\x28\x8c\xbc" => 'bar']);
3848
}
3949

50+
/**
51+
* @param array<mixed> $data
52+
*/
53+
#[Test]
54+
#[DataProvider('provideUnrepresentableFloats')]
55+
public function it_throws_when_serializing_a_float_json_cannot_represent(array $data): void
56+
{
57+
$this->expectException(SerializationFailed::class);
58+
$this->expectExceptionMessageIs('Failed to serialize data to JSON. Error code: 7, error message: Inf and NaN cannot be JSON encoded.');
59+
60+
$this->serializer->serialize($data);
61+
}
62+
63+
/**
64+
* @return iterable<string, array{array<mixed>}>
65+
*/
66+
public static function provideUnrepresentableFloats(): iterable
67+
{
68+
yield 'infinity' => [[\INF]];
69+
yield 'negative infinity' => [[-\INF]];
70+
yield 'not a number' => [[\NAN]];
71+
}
72+
4073
#[Test]
4174
public function it_can_deserialize_from_a_json_string_to_php_scalars(): void
4275
{
@@ -45,6 +78,25 @@ public function it_can_deserialize_from_a_json_string_to_php_scalars(): void
4578
$this->assertSame(['foo' => 'bar', 'baz' => [1.03, true, 'foobar']], $result);
4679
}
4780

81+
/**
82+
* @param array<mixed> $expected
83+
*/
84+
#[Test]
85+
#[DataProvider('provideEmptyStructures')]
86+
public function it_can_deserialize_an_empty_structure(string $json, array $expected): void
87+
{
88+
$this->assertSame($expected, $this->serializer->deserialize($json));
89+
}
90+
91+
/**
92+
* @return iterable<string, array{string, array<mixed>}>
93+
*/
94+
public static function provideEmptyStructures(): iterable
95+
{
96+
yield 'empty array' => ['[]', []];
97+
yield 'empty object' => ['{}', []];
98+
}
99+
48100
#[Test]
49101
public function it_throws_when_deserializing_if_an_error_encountered(): void
50102
{
@@ -55,14 +107,74 @@ public function it_throws_when_deserializing_if_an_error_encountered(): void
55107
}
56108

57109
#[Test]
58-
public function it_throws_when_deserializing_if_the_result_is_not_an_array(): void
110+
public function it_can_deserialize_json_nested_to_the_maximum_supported_depth(): void
111+
{
112+
$result = $this->serializer->deserialize($this->nestedArrays(self::MAX_NESTING_DEPTH));
113+
114+
$this->assertCount(1, $result);
115+
}
116+
117+
#[Test]
118+
public function it_throws_when_deserializing_json_nested_beyond_the_maximum_supported_depth(): void
119+
{
120+
$this->expectException(DeserializationFailed::class);
121+
$this->expectExceptionMessageIs('Failed to deserialize data from JSON. Error code: 1, error message: Maximum stack depth exceeded.');
122+
123+
$this->serializer->deserialize($this->nestedArrays(self::MAX_NESTING_DEPTH + 1));
124+
}
125+
126+
#[Test]
127+
#[DataProvider('provideNonArrayRoots')]
128+
public function it_throws_when_deserializing_if_the_result_is_not_an_array(string $json, string $expectedType): void
59129
{
60130
$this->expectException(InvalidDeserializedData::class);
61-
$this->expectExceptionMessageIs('Expected array when deserializing JSON, got "boolean".');
131+
$this->expectExceptionMessageIs(\sprintf('Expected array when deserializing JSON, got "%s".', $expectedType));
132+
133+
$this->serializer->deserialize($json);
134+
}
135+
136+
/**
137+
* @return iterable<string, array{string, string}>
138+
*/
139+
public static function provideNonArrayRoots(): iterable
140+
{
141+
yield 'true' => ['true', 'boolean'];
142+
yield 'false' => ['false', 'boolean'];
143+
yield 'null' => ['null', 'NULL'];
144+
yield 'integer' => ['5', 'integer'];
145+
yield 'float' => ['1.5', 'double'];
146+
yield 'string' => ['"foo"', 'string'];
147+
}
148+
149+
#[Test]
150+
public function a_serialization_error_can_be_caught_as_a_failure(): void
151+
{
152+
$this->expectException(Failure::class);
153+
154+
$this->serializer->serialize(["\xf0\x28\x8c\xbc" => 'bar']);
155+
}
156+
157+
#[Test]
158+
public function a_deserialization_error_can_be_caught_as_a_failure(): void
159+
{
160+
$this->expectException(Failure::class);
161+
162+
$this->serializer->deserialize('{"unclosed":"bad","object":"json"');
163+
}
164+
165+
#[Test]
166+
public function invalid_deserialized_data_can_be_caught_as_a_failure(): void
167+
{
168+
$this->expectException(Failure::class);
62169

63170
$this->serializer->deserialize('true');
64171
}
65172

173+
private function nestedArrays(int $depth): string
174+
{
175+
return \str_repeat('[', $depth).\str_repeat(']', $depth);
176+
}
177+
66178
protected function setUp(): void
67179
{
68180
$this->serializer = new Serializer();

0 commit comments

Comments
 (0)