1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace ocramius\utilTest; |
6
|
|
|
|
7
|
|
|
use Exception; |
8
|
|
|
use ocramius\util\exception\NoSuchElementException; |
9
|
|
|
use ocramius\util\exception\NullPointerException; |
10
|
|
|
use ocramius\util\Optional; |
11
|
|
|
use PHPUnit\Framework\TestCase; |
12
|
|
|
use ReflectionException; |
13
|
|
|
use stdClass; |
14
|
|
|
use Throwable; |
15
|
|
|
|
16
|
|
|
use function assert; |
17
|
|
|
use function is_callable; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Tests for {@see \ocramius\util\Optional} |
21
|
|
|
* |
22
|
|
|
* @covers \ocramius\util\Optional |
23
|
|
|
*/ |
24
|
|
|
class OptionalTest extends TestCase |
25
|
|
|
{ |
26
|
|
|
public function testNewEmptyAlwaysProducesSameInstance(): void |
27
|
|
|
{ |
28
|
|
|
$this->assertSame(Optional::newEmpty(), Optional::newEmpty()); |
29
|
|
|
} |
30
|
|
|
|
31
|
|
|
public function testNewEmptyProducesEmptyInstance(): void |
32
|
|
|
{ |
33
|
|
|
$this->assertFalse(Optional::newEmpty()->isPresent()); |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
public function testOfNullableFromEmptyValueProducesAnEmptyInstance(): void |
37
|
|
|
{ |
38
|
|
|
$this->assertSame(Optional::newEmpty(), Optional::ofNullable(null)); |
39
|
|
|
} |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* @param mixed $value |
43
|
|
|
* |
44
|
|
|
* @dataProvider getValidValues |
45
|
|
|
*/ |
46
|
|
|
public function testOfNullableFromNonEmptyValueProducesNonEmptyInstance($value): void |
47
|
|
|
{ |
48
|
|
|
$optional = Optional::ofNullable($value); |
49
|
|
|
|
50
|
|
|
$this->assertNotSame(Optional::newEmpty(), $optional); |
51
|
|
|
|
52
|
|
|
$this->assertTrue($optional->isPresent()); |
53
|
|
|
$this->assertSame($value, $optional->get()); |
54
|
|
|
} |
55
|
|
|
|
56
|
|
|
public function testOfFromEmptyValueCausesExceptionWhenDisallowed(): void |
57
|
|
|
{ |
58
|
|
|
$this->expectException(NullPointerException::class); |
59
|
|
|
|
60
|
|
|
Optional::of(null); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @param mixed $value |
65
|
|
|
* |
66
|
|
|
* @dataProvider getValidValues |
67
|
|
|
*/ |
68
|
|
|
public function testOfFromNonEmptyValueProducesNonEmptyInstance($value): void |
69
|
|
|
{ |
70
|
|
|
$optional = Optional::of($value); |
71
|
|
|
|
72
|
|
|
$this->assertNotSame(Optional::newEmpty(), $optional); |
73
|
|
|
|
74
|
|
|
$this->assertTrue($optional->isPresent()); |
75
|
|
|
$this->assertSame($value, $optional->get()); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
public function testEmptyValueDisallowsGettingWrappedValue(): void |
79
|
|
|
{ |
80
|
|
|
$this->expectException(NoSuchElementException::class); |
81
|
|
|
|
82
|
|
|
Optional::newEmpty()->get(); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @throws ReflectionException |
87
|
|
|
*/ |
88
|
|
|
public function testIfPresentIsNotExecutedIfValueIsNotPresent(): void |
89
|
|
|
{ |
90
|
|
|
$neverCalled = $this->getMockBuilder(stdClass::class) |
|
|
|
|
91
|
|
|
->setMethods(['__invoke']) |
92
|
|
|
->getMock(); |
93
|
|
|
assert(is_callable($neverCalled) || $neverCalled instanceof MockObject); |
|
|
|
|
94
|
|
|
|
95
|
|
|
$neverCalled->expects($this->never())->method('__invoke'); |
96
|
|
|
|
97
|
|
|
Optional::newEmpty()->ifPresent($neverCalled); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* @param mixed $value |
102
|
|
|
* |
103
|
|
|
* @throws ReflectionException |
104
|
|
|
* |
105
|
|
|
* @dataProvider getValidValues |
106
|
|
|
*/ |
107
|
|
|
public function testIfPresentIsExecutedWhenValueIsPresent($value): void |
108
|
|
|
{ |
109
|
|
|
$calledOnce = $this->getMockBuilder(stdClass::class) |
|
|
|
|
110
|
|
|
->setMethods(['__invoke']) |
111
|
|
|
->getMock(); |
112
|
|
|
assert(is_callable($calledOnce) || $calledOnce instanceof MockObject); |
|
|
|
|
113
|
|
|
|
114
|
|
|
$calledOnce->expects($this->once())->method('__invoke')->with($value); |
115
|
|
|
|
116
|
|
|
Optional::of($value)->ifPresent($calledOnce); |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
/** |
120
|
|
|
* @throws ReflectionException |
121
|
|
|
*/ |
122
|
|
|
public function testFilterIsNotExecutedIfValueIsNotPresent(): void |
123
|
|
|
{ |
124
|
|
|
$neverCalled = $this->getMockBuilder(stdClass::class) |
|
|
|
|
125
|
|
|
->setMethods(['__invoke']) |
126
|
|
|
->getMock(); |
127
|
|
|
assert(is_callable($neverCalled) || $neverCalled instanceof MockObject); |
|
|
|
|
128
|
|
|
|
129
|
|
|
$neverCalled->expects($this->never())->method('__invoke'); |
130
|
|
|
|
131
|
|
|
$this->assertSame(Optional::newEmpty(), Optional::newEmpty()->filter($neverCalled)); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* @param mixed $value |
136
|
|
|
* |
137
|
|
|
* @throws ReflectionException |
138
|
|
|
* |
139
|
|
|
* @dataProvider getValidValues |
140
|
|
|
*/ |
141
|
|
|
public function testFilteringProducesEmptyOptionalWhenValueIsNotAccepted($value): void |
142
|
|
|
{ |
143
|
|
|
$falseFilter = $this->getMockBuilder(stdClass::class) |
|
|
|
|
144
|
|
|
->setMethods(['__invoke']) |
145
|
|
|
->getMock(); |
146
|
|
|
assert(is_callable($falseFilter) || $falseFilter instanceof MockObject); |
|
|
|
|
147
|
|
|
|
148
|
|
|
$falseFilter->expects($this->once())->method('__invoke')->with($value)->will($this->returnValue(false)); |
149
|
|
|
|
150
|
|
|
$this->assertSame(Optional::newEmpty(), Optional::of($value)->filter($falseFilter)); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* @param mixed $value |
155
|
|
|
* |
156
|
|
|
* @throws ReflectionException |
157
|
|
|
* |
158
|
|
|
* @dataProvider getValidValues |
159
|
|
|
*/ |
160
|
|
|
public function testFilteringProducesSameOptionalInstanceWhenValueIsAccepted($value): void |
161
|
|
|
{ |
162
|
|
|
$falseFilter = $this->getMockBuilder(stdClass::class) |
|
|
|
|
163
|
|
|
->setMethods(['__invoke']) |
164
|
|
|
->getMock(); |
165
|
|
|
assert(is_callable($falseFilter) || $falseFilter instanceof MockObject); |
|
|
|
|
166
|
|
|
$optional = Optional::of($value); |
167
|
|
|
|
168
|
|
|
$falseFilter->expects($this->once())->method('__invoke')->with($value)->will($this->returnValue(true)); |
169
|
|
|
|
170
|
|
|
$this->assertSame($optional, $optional->filter($falseFilter)); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* @throws ReflectionException |
175
|
|
|
*/ |
176
|
|
|
public function testMappingEmptyOptionalProducesEmptyOptional(): void |
177
|
|
|
{ |
178
|
|
|
$neverCalled = $this->getMockBuilder(stdClass::class) |
|
|
|
|
179
|
|
|
->setMethods(['__invoke']) |
180
|
|
|
->getMock(); |
181
|
|
|
assert(is_callable($neverCalled) || $neverCalled instanceof MockObject); |
|
|
|
|
182
|
|
|
|
183
|
|
|
$neverCalled->expects($this->never())->method('__invoke'); |
184
|
|
|
|
185
|
|
|
$this->assertSame(Optional::newEmpty(), Optional::newEmpty()->map($neverCalled)); |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* @param mixed $value |
190
|
|
|
* |
191
|
|
|
* @throws ReflectionException |
192
|
|
|
* |
193
|
|
|
* @dataProvider getValidValues |
194
|
|
|
*/ |
195
|
|
|
public function testMappingNonEmptyValuesProducesOptionalWithMapMethodReturnValue($value): void |
196
|
|
|
{ |
197
|
|
|
$mappedValue = new stdClass(); |
198
|
|
|
$mapper = $this->getMockBuilder(stdClass::class) |
|
|
|
|
199
|
|
|
->setMethods(['__invoke']) |
200
|
|
|
->getMock(); |
201
|
|
|
assert(is_callable($mapper) || $mapper instanceof MockObject); |
|
|
|
|
202
|
|
|
|
203
|
|
|
$mapper->expects($this->once())->method('__invoke')->with($value)->will($this->returnValue($mappedValue)); |
204
|
|
|
|
205
|
|
|
$optional = Optional::of($value)->map($mapper); |
206
|
|
|
|
207
|
|
|
$this->assertNotSame(Optional::newEmpty(), $optional); |
208
|
|
|
$this->assertSame($mappedValue, $optional->get()); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* @throws ReflectionException |
213
|
|
|
*/ |
214
|
|
|
public function testMappingNonEmptyValuesMayProduceEmptyOptional(): void |
215
|
|
|
{ |
216
|
|
|
$mapper = $this->getMockBuilder(stdClass::class) |
|
|
|
|
217
|
|
|
->setMethods(['__invoke']) |
218
|
|
|
->getMock(); |
219
|
|
|
assert(is_callable($mapper) || $mapper instanceof MockObject); |
|
|
|
|
220
|
|
|
|
221
|
|
|
$mapper->expects($this->once())->method('__invoke')->will($this->returnValue(null)); |
222
|
|
|
|
223
|
|
|
$this->assertSame(Optional::newEmpty(), Optional::of(new stdClass())->map($mapper)); |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* @throws ReflectionException |
228
|
|
|
*/ |
229
|
|
|
public function testFlatMappingEmptyOptionalProducesEmptyOptional(): void |
230
|
|
|
{ |
231
|
|
|
$neverCalled = $this->getMockBuilder(stdClass::class) |
|
|
|
|
232
|
|
|
->setMethods(['__invoke']) |
233
|
|
|
->getMock(); |
234
|
|
|
assert(is_callable($neverCalled) || $neverCalled instanceof MockObject); |
|
|
|
|
235
|
|
|
|
236
|
|
|
$neverCalled->expects($this->never())->method('__invoke'); |
237
|
|
|
|
238
|
|
|
$this->assertSame(Optional::newEmpty(), Optional::newEmpty()->flatMap($neverCalled)); |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* @param mixed $value |
243
|
|
|
* |
244
|
|
|
* @throws ReflectionException |
245
|
|
|
* |
246
|
|
|
* @dataProvider getValidValues |
247
|
|
|
*/ |
248
|
|
|
public function testFlatMappingNonEmptyOptionalProducesNonEmptyOptional($value): void |
249
|
|
|
{ |
250
|
|
|
$mappedValue = Optional::ofNullable(new stdClass()); |
251
|
|
|
$mapper = $this->getMockBuilder(stdClass::class) |
|
|
|
|
252
|
|
|
->setMethods(['__invoke']) |
253
|
|
|
->getMock(); |
254
|
|
|
assert(is_callable($mapper) || $mapper instanceof MockObject); |
|
|
|
|
255
|
|
|
|
256
|
|
|
$mapper->expects($this->once())->method('__invoke')->with($value)->will($this->returnValue($mappedValue)); |
257
|
|
|
|
258
|
|
|
$this->assertSame($mappedValue, Optional::of($value)->flatMap($mapper)); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
/** |
262
|
|
|
* @throws ReflectionException |
263
|
|
|
*/ |
264
|
|
|
public function testFlatMappingNonEmptyOptionalDisallowsEmptyMapperResult(): void |
265
|
|
|
{ |
266
|
|
|
$mapper = $this->getMockBuilder(stdClass::class) |
|
|
|
|
267
|
|
|
->setMethods(['__invoke']) |
268
|
|
|
->getMock(); |
269
|
|
|
assert(is_callable($mapper) || $mapper instanceof MockObject); |
|
|
|
|
270
|
|
|
|
271
|
|
|
$mapper->expects($this->once())->method('__invoke')->will($this->returnValue(null)); |
272
|
|
|
|
273
|
|
|
$this->expectException(NullPointerException::class); |
274
|
|
|
|
275
|
|
|
Optional::of(new stdClass())->flatMap($mapper); |
276
|
|
|
} |
277
|
|
|
|
278
|
|
|
/** |
279
|
|
|
* @param mixed $value |
280
|
|
|
* |
281
|
|
|
* @dataProvider getValidValues |
282
|
|
|
*/ |
283
|
|
|
public function testOrElseRetrievesGivenValueOnEmptyOptional($value): void |
284
|
|
|
{ |
285
|
|
|
$this->assertSame($value, Optional::newEmpty()->orElse($value)); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* @param mixed $value |
290
|
|
|
* |
291
|
|
|
* @dataProvider getValidValues |
292
|
|
|
*/ |
293
|
|
|
public function testOrElseRetrievesOptionalValueWhenValueIsPresent($value): void |
294
|
|
|
{ |
295
|
|
|
$this->assertSame($value, Optional::of($value)->orElse(new stdClass())); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
|
|
* @param mixed $value |
300
|
|
|
* |
301
|
|
|
* @throws ReflectionException |
302
|
|
|
* |
303
|
|
|
* @dataProvider getValidValues |
304
|
|
|
*/ |
305
|
|
|
public function testOrElseGetRetrievesCallableReturnValueOnEmptyOptional($value): void |
306
|
|
|
{ |
307
|
|
|
$fallback = $this->getMockBuilder(stdClass::class) |
|
|
|
|
308
|
|
|
->setMethods(['__invoke']) |
309
|
|
|
->getMock(); |
310
|
|
|
assert(is_callable($fallback) || $fallback instanceof MockObject); |
|
|
|
|
311
|
|
|
|
312
|
|
|
$fallback->expects($this->once())->method('__invoke')->will($this->returnValue($value)); |
313
|
|
|
|
314
|
|
|
$this->assertSame($value, Optional::newEmpty()->orElseGet($fallback)); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
/** |
318
|
|
|
* @param mixed $value |
319
|
|
|
* |
320
|
|
|
* @throws ReflectionException |
321
|
|
|
* @throws Exception |
322
|
|
|
* |
323
|
|
|
* @dataProvider getValidValues |
324
|
|
|
*/ |
325
|
|
|
public function testOrElseThrowRetrievesGivenValueWhenValueIsAvailable($value): void |
326
|
|
|
{ |
327
|
|
|
$exceptionFactory = $this->getMockBuilder(stdClass::class) |
|
|
|
|
328
|
|
|
->setMethods(['__invoke']) |
329
|
|
|
->getMock(); |
330
|
|
|
assert(is_callable($exceptionFactory) || $exceptionFactory instanceof MockObject); |
|
|
|
|
331
|
|
|
|
332
|
|
|
$exceptionFactory->expects($this->never())->method('__invoke'); |
333
|
|
|
|
334
|
|
|
$this->assertSame($value, Optional::of($value)->orElseThrow($exceptionFactory)); |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
/** |
338
|
|
|
* @throws ReflectionException |
339
|
|
|
*/ |
340
|
|
|
public function testOrElseThrowThrowsExceptionOnEmptyOptional(): void |
341
|
|
|
{ |
342
|
|
|
$exception = new Exception(); |
343
|
|
|
$exceptionFactory = $this->getMockBuilder(stdClass::class) |
|
|
|
|
344
|
|
|
->setMethods(['__invoke']) |
345
|
|
|
->getMock(); |
346
|
|
|
assert(is_callable($exceptionFactory) || $exceptionFactory instanceof MockObject); |
|
|
|
|
347
|
|
|
|
348
|
|
|
$exceptionFactory->expects($this->once())->method('__invoke')->will($this->returnValue($exception)); |
349
|
|
|
|
350
|
|
|
try { |
351
|
|
|
Optional::newEmpty()->orElseThrow($exceptionFactory); |
352
|
|
|
|
353
|
|
|
$this->fail('No exception was thrown, expected Optional#orElseThrow() to throw one'); |
354
|
|
|
} catch (Throwable $caught) { |
355
|
|
|
$this->assertSame($exception, $caught); |
356
|
|
|
} |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
/** |
360
|
|
|
* @param mixed $value |
361
|
|
|
* |
362
|
|
|
* @throws ReflectionException |
363
|
|
|
* |
364
|
|
|
* @dataProvider getValidValues |
365
|
|
|
*/ |
366
|
|
|
public function testOrElseGetRetrievesOptionalValueIfValueIsPresent($value): void |
367
|
|
|
{ |
368
|
|
|
$fallback = $this->getMockBuilder(stdClass::class) |
|
|
|
|
369
|
|
|
->setMethods(['__invoke']) |
370
|
|
|
->getMock(); |
371
|
|
|
assert(is_callable($fallback) || $fallback instanceof MockObject); |
|
|
|
|
372
|
|
|
|
373
|
|
|
$fallback->expects($this->never())->method('__invoke'); |
374
|
|
|
|
375
|
|
|
$this->assertSame($value, Optional::of($value)->orElseGet($fallback)); |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
public function testInequality(): void |
379
|
|
|
{ |
380
|
|
|
$value1 = new stdClass(); |
381
|
|
|
$value2 = new stdClass(); |
382
|
|
|
|
383
|
|
|
$this->assertFalse(Optional::of($value1)->equals(Optional::of($value2))); |
384
|
|
|
$this->assertFalse(Optional::of($value1)->equals($value1)); |
385
|
|
|
$this->assertFalse(Optional::of($value1)->equals('foo')); |
386
|
|
|
$this->assertTrue(Optional::newEmpty()->equals(Optional::newEmpty())); |
387
|
|
|
} |
388
|
|
|
|
389
|
|
|
/** |
390
|
|
|
* @param mixed $value |
391
|
|
|
* |
392
|
|
|
* @dataProvider getValidValues |
393
|
|
|
*/ |
394
|
|
|
public function testEquals($value): void |
395
|
|
|
{ |
396
|
|
|
$this->assertTrue(Optional::of($value)->equals(Optional::of($value))); |
397
|
|
|
} |
398
|
|
|
|
399
|
|
|
/** |
400
|
|
|
* @param mixed $value |
401
|
|
|
* |
402
|
|
|
* @dataProvider getValidValues |
403
|
|
|
*/ |
404
|
|
|
public function testInequalityWithEmptyOptional($value): void |
405
|
|
|
{ |
406
|
|
|
$this->assertFalse(Optional::of($value)->equals(Optional::newEmpty())); |
407
|
|
|
$this->assertFalse(Optional::newEmpty()->equals(Optional::of($value))); |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
public function testStringCast(): void |
411
|
|
|
{ |
412
|
|
|
$this->assertSame('Optional.empty', (string) Optional::newEmpty()); |
413
|
|
|
$this->assertSame('Optional[foo]', (string) Optional::of('foo')); |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
/** |
417
|
|
|
* Data provider: provides valid Optional values |
418
|
|
|
* |
419
|
|
|
* @return mixed[][] |
420
|
|
|
* |
421
|
|
|
* @throws ReflectionException |
422
|
|
|
*/ |
423
|
|
|
public function getValidValues(): array |
424
|
|
|
{ |
425
|
|
|
return [ |
426
|
|
|
[new stdClass()], |
427
|
|
|
[$this->createMock(stdClass::class)], |
428
|
|
|
[''], |
429
|
|
|
['foo'], |
430
|
|
|
[123], |
431
|
|
|
[123.456], |
432
|
|
|
[['foo', 'bar']], |
433
|
|
|
[[]], |
434
|
|
|
]; |
435
|
|
|
} |
436
|
|
|
} |
437
|
|
|
|
This method has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.