Passed
Push — master ( 162c90...8e9314 )
by Alex
59s queued 11s
created

getCreateFromFormatWillThrowDateFactoryExceptionIfNotAbleToCreateADateTimeData()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 6
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ArpTest\DateTime;
6
7
use Arp\DateTime\DateFactory;
8
use Arp\DateTime\DateFactoryInterface;
9
use Arp\DateTime\DateIntervalFactoryInterface;
10
use Arp\DateTime\DateTimeFactoryInterface;
11
use Arp\DateTime\Exception\DateFactoryException;
12
use Arp\DateTime\Exception\DateIntervalFactoryException;
13
use Arp\DateTime\Exception\DateTimeFactoryException;
14
use PHPUnit\Framework\MockObject\MockObject;
15
use PHPUnit\Framework\TestCase;
16
17
/**
18
 * @covers  \Arp\DateTime\DateFactory
19
 *
20
 * @author  Alex Patterson <[email protected]>
21
 * @package ArpTest\DateTime
22
 */
23
final class DateFactoryTest extends TestCase
24
{
25
    /**
26
     * @var DateTimeFactoryInterface&MockObject
27
     */
28
    private $dateTimeFactory;
29
30
    /**
31
     * @var DateIntervalFactoryInterface&MockObject
32
     */
33
    private $dateIntervalFactory;
34
35
    /**
36
     * Set up the test case dependencies
37
     */
38
    public function setUp(): void
39
    {
40
        $this->dateTimeFactory = $this->createMock(DateTimeFactoryInterface::class);
41
42
        $this->dateIntervalFactory = $this->createMock(DateIntervalFactoryInterface::class);
43
    }
44
45
    /**
46
     * Ensure that the factory implements DateFactoryInterface
47
     */
48
    public function testImplementsDateFactoryInterface(): void
49
    {
50
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
51
52
        $this->assertInstanceOf(DateFactoryInterface::class, $factory);
53
    }
54
55
    /**
56
     * Assert that if the \DateTime instance cannot be created a DateTimeFactoryException will be thrown
57
     *
58
     * @throws DateFactoryException
59
     */
60
    public function testCreateDateTimeWillThrowDateFactoryExceptionIfNotAbleToCreateADateTime(): void
61
    {
62
        $spec = 'foo';
63
        $timeZone = null;
64
        $exceptionMessage = sprintf('Failed to create a valid \DateTime instance using \'%s\'', $spec);
65
66
        $dateTimeFactory = $this->createMockForCreateDateTime($exceptionMessage);
67
68
        $factory = new DateFactory($dateTimeFactory, $this->dateIntervalFactory);
69
70
        $this->expectException(DateFactoryException::class);
71
        $this->expectExceptionMessage($exceptionMessage);
72
73
        $factory->createDateTime($spec, $timeZone);
74
    }
75
76
    /**
77
     * Ensure that calls to createDateTime() will return the valid configured \DateTime instance
78
     *
79
     * @param string|null               $spec     The date and time specification
80
     * @param \DateTimeZone|string|null $timeZone The optional date time zone to test
81
     *
82
     * @dataProvider getCreateDateTimeData
83
     *
84
     * @throws DateFactoryException
85
     * @throws \Exception
86
     */
87
    public function testCreateDateTime(?string $spec, $timeZone = null): void
88
    {
89
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
90
91
        /** @var \DateTimeInterface $expectedDateTime */
92
        $expectedDateTime = new \DateTime(
93
            $spec ?? 'now',
94
            is_string($timeZone) ? new \DateTimeZone($timeZone) : $timeZone
95
        );
96
97
        $this->dateTimeFactory->expects($this->once())
98
            ->method('createDateTime')
99
            ->with($spec, $timeZone)
100
            ->willReturn($expectedDateTime);
101
102
        $dateTime = $factory->createDateTime($spec, $timeZone);
103
104
        $this->assertSame($expectedDateTime, $dateTime);
105
    }
106
107
    /**
108
     * @return array[]
109
     */
110
    public function getCreateDateTimeData(): array
111
    {
112
        return [
113
            [
114
                null,
115
            ],
116
            [
117
                'now',
118
            ],
119
            [
120
                'now',
121
                'Europe/London',
122
            ],
123
            [
124
                '2019-05-14 12:33:00',
125
            ],
126
            [
127
                '2019-08-14 17:34:55',
128
                'UTC',
129
            ],
130
            [
131
                '2020-08-22 14:43:12',
132
                null,
133
            ],
134
            [
135
                '2020-08-22 14:44:37',
136
                new \DateTimeZone('Europe/London'),
137
            ],
138
            [
139
                '2000-01-01',
140
                'Pacific/Nauru',
141
            ],
142
            [
143
                'now',
144
                new \DateTimeZone('America/New_York'),
145
            ],
146
        ];
147
    }
148
149
    /**
150
     * Assert that if the \DateTime instance cannot be created a DateTimeFactoryException will be thrown
151
     *
152
     * @param string                    $spec
153
     * @param string                    $format
154
     * @param null|string|\DateTimeZone $timeZone
155
     *
156
     * @dataProvider getCreateFromFormatWillThrowDateFactoryExceptionIfNotAbleToCreateADateTimeData
157
     *
158
     * @throws DateFactoryException
159
     */
160
    public function testCreateFromFormatWillThrowDateFactoryExceptionIfNotAbleToCreateADateTime(
161
        string $spec,
162
        string $format,
163
        $timeZone = null
164
    ): void {
165
        $exceptionMessage = sprintf(
166
            'Failed to create a valid \DateTime instance using \'%s\' and format \'%s\'',
167
            $spec,
168
            $format
169
        );
170
171
        // Note that DateTimeInterface cannot be mocked by PHPUnit, so we can manually create a stub
172
        $dateTimeFactory = $this->createMockForCreateFromFormat($exceptionMessage);
173
174
        $factory = new DateFactory($dateTimeFactory, $this->dateIntervalFactory);
175
176
        $this->expectException(DateFactoryException::class);
177
        $this->expectExceptionMessage($exceptionMessage);
178
179
        $factory->createFromFormat($spec, $format, $timeZone);
180
    }
181
182
    /**
183
     * @return array[]
184
     */
185
    public function getCreateFromFormatWillThrowDateFactoryExceptionIfNotAbleToCreateADateTimeData(): array
186
    {
187
        return [
188
            [
189
                'foo',
190
                'xyz',
191
            ],
192
        ];
193
    }
194
195
    /**
196
     * Assert that if providing an invalid $spec to createDateTimeZone() a DateFactoryException is thrown
197
     *
198
     * @param string $spec
199
     *
200
     * @dataProvider getCreateDateTimeZoneWillThrowDateFactoryExceptionIfSpecIsInvalidData
201
     *
202
     * @throws DateFactoryException
203
     */
204
    public function testCreateDateTimeZoneWillThrowDateFactoryExceptionIfSpecIsInvalid(string $spec): void
205
    {
206
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
207
208
        $exceptionCode = 456;
209
        $exceptionMessage = 'This is a test exception message';
210
        $exception = new DateTimeFactoryException($exceptionMessage, $exceptionCode);
211
212
        $this->dateTimeFactory->expects($this->once())
213
            ->method('createDateTimeZone')
214
            ->with($spec)
215
            ->willThrowException($exception);
216
217
        $this->expectException(DateFactoryException::class);
218
        $this->expectExceptionMessage($exceptionMessage);
219
        $this->expectExceptionCode($exceptionCode);
220
221
        $factory->createDateTimeZone($spec);
222
    }
223
224
    /**
225
     * @return array[]
226
     */
227
    public function getCreateDateTimeZoneWillThrowDateFactoryExceptionIfSpecIsInvalidData(): array
228
    {
229
        return [
230
            [
231
                'foo',
232
            ],
233
            [
234
                '123',
235
            ],
236
        ];
237
    }
238
239
    /**
240
     * @param string $spec
241
     *
242
     * @dataProvider getCreateDateTimeZoneData
243
     *
244
     * @throws DateFactoryException
245
     */
246
    public function testCreateDateTimeZone(string $spec): void
247
    {
248
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
249
250
        $dateTimeZone = new \DateTimeZone($spec);
251
252
        $this->dateTimeFactory->expects($this->once())
253
            ->method('createDateTimeZone')
254
            ->with($spec)
255
            ->willReturn($dateTimeZone);
256
257
        $this->assertSame($dateTimeZone, $factory->createDateTimeZone($spec));
258
    }
259
260
    /**
261
     * @return array[]
262
     */
263
    public function getCreateDateTimeZoneData(): array
264
    {
265
        return [
266
            [
267
                'UTC',
268
            ],
269
            [
270
                'Europe/London',
271
            ],
272
            [
273
                'Europe/Amsterdam',
274
            ],
275
            [
276
                'Europe/Rome',
277
            ],
278
            [
279
                'Atlantic/Bermuda',
280
            ],
281
            [
282
                'Atlantic/Azores',
283
            ],
284
            [
285
                'Antarctica/DumontDUrville',
286
            ],
287
        ];
288
    }
289
290
    /**
291
     * Assert that calls to creatDateInterval() will return the expected DateInterval instance
292
     *
293
     * @param string $spec
294
     *
295
     * @dataProvider getCreateDateIntervalWillReturnANewDateIntervalToSpecData
296
     *
297
     * @throws DateFactoryException
298
     */
299
    public function testCreateDateIntervalWillReturnANewDateIntervalToSpec(string $spec): void
300
    {
301
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
302
303
        /** @var \DateInterval|MockObject $dateInterval */
304
        $dateInterval = $this->createMock(\DateInterval::class);
305
306
        $this->dateIntervalFactory->expects($this->once())
307
            ->method('createDateInterval')
308
            ->willReturn($dateInterval);
309
310
        $this->assertSame($dateInterval, $factory->createDateInterval($spec));
311
    }
312
313
    /**
314
     * @return array[]
315
     */
316
    public function getCreateDateIntervalWillReturnANewDateIntervalToSpecData(): array
317
    {
318
        return [
319
            ['P100D'],
320
            ['P4Y1DT9H11M3S'],
321
            ['P2Y4DT6H8M'],
322
            ['P7Y8M'],
323
        ];
324
    }
325
326
    /**
327
     * Assert that an invalid $spec passed to createDateInterval() will raise a DateTimeFactoryException
328
     *
329
     * @throws DateFactoryException
330
     */
331
    public function testDateIntervalWillThrowDateTimeFactoryExceptionIfUnableToCreateADateInterval(): void
332
    {
333
        $spec = 'Hello';
334
335
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
336
337
        $exceptionCode = 123;
338
        $exceptionMessage = 'This is a test exception message';
339
        $exception = new DateIntervalFactoryException($exceptionMessage, $exceptionCode);
340
341
        $this->dateIntervalFactory->expects($this->once())
342
            ->method('createDateInterval')
343
            ->willThrowException($exception);
344
345
        $this->expectDeprecationMessage(DateTimeFactoryException::class);
346
        $this->expectExceptionMessage($exceptionMessage);
347
        $this->expectExceptionCode($exceptionCode);
348
349
        $factory->createDateInterval($spec);
350
    }
351
352
    /**
353
     * Assert that the calls to diff will return a valid DateInterval
354
     *
355
     * @param \DateTime $origin
356
     * @param \DateTime $target
357
     * @param bool      $absolute
358
     *
359
     * @dataProvider getDiffWillReturnDateIntervalData
360
     *
361
     * @throws DateFactoryException
362
     */
363
    public function testDiffWillReturnDateInterval(\DateTime $origin, \DateTime $target, bool $absolute = false): void
364
    {
365
        $dateInterval = $origin->diff($target);
366
367
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
368
369
        $this->dateIntervalFactory->expects($this->once())
370
            ->method('diff')
371
            ->with($origin, $target, $absolute)
372
            ->willReturn($dateInterval);
373
374
        $this->assertSame($dateInterval, $factory->diff($origin, $target, $absolute));
375
    }
376
377
    /**
378
     * @return array[]
379
     */
380
    public function getDiffWillReturnDateIntervalData(): array
381
    {
382
        return [
383
            [
384
                new \DateTime('1984-01-01'),
385
                new \DateTime('2020-01-01'),
386
            ],
387
            [
388
                new \DateTime('2017-08-12'),
389
                new \DateTime('2019-01-19'),
390
                true,
391
            ],
392
            [
393
                new \DateTime('2020-01-01 13:33:47'),
394
                new \DateTime('2020-08-30 01:25:33'),
395
                false,
396
            ],
397
        ];
398
    }
399
400
    /**
401
     * Assert that a DateTimeFactoryException is thrown when unable to diff the provided dates
402
     *
403
     * @throws DateFactoryException
404
     */
405
    public function testDateTimeFactoryExceptionWillBeThrownIfDiffFails(): void
406
    {
407
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
408
409
        $origin = new \DateTime();
410
        $target = new \DateTime();
411
412
        $exceptionCode = 123;
413
        $exceptionMessage = 'This is a test exception message';
414
        $exception = new DateIntervalFactoryException($exceptionMessage, $exceptionCode);
415
416
        $this->dateIntervalFactory->expects($this->once())
417
            ->method('diff')
418
            ->with($origin, $target, false)
419
            ->willThrowException($exception);
420
421
        $this->expectException(DateFactoryException::class);
422
        $this->expectExceptionMessage($exceptionMessage);
423
        $this->expectExceptionCode($exceptionCode);
424
425
        $factory->diff($origin, $target);
426
    }
427
428
    /**
429
     * @param string $exceptionMessage
430
     *
431
     * @return DateTimeFactoryInterface
432
     */
433
    private function createMockForCreateDateTime(string $exceptionMessage): DateTimeFactoryInterface
434
    {
435
        return new class ($exceptionMessage) implements DateTimeFactoryInterface {
436
            private string $exceptionMessage;
437
438
            public function __construct(string $exceptionMessage)
439
            {
440
                $this->exceptionMessage = $exceptionMessage;
441
            }
442
443
            public function createFromFormat(string $spec, string $format, $timeZone = null): \DateTimeInterface
444
            {
445
                /** @phpstan-ignore-next-line */
446
                return \DateTime::createFromFormat($spec, $format);
447
            }
448
449
            public function createDateTimeZone(string $spec): \DateTimeZone
450
            {
451
                return new \DateTimeZone($spec);
452
            }
453
454
            public function createDateTime(string $spec = null, $timeZone = null): \DateTimeInterface
455
            {
456
                throw new DateTimeFactoryException($this->exceptionMessage);
457
            }
458
        };
459
    }
460
461
    /**
462
     * @param string $exceptionMessage
463
     *
464
     * @return DateTimeFactoryInterface
465
     */
466
    private function createMockForCreateFromFormat(string $exceptionMessage): DateTimeFactoryInterface
467
    {
468
        return new class ($exceptionMessage) implements DateTimeFactoryInterface {
469
            private string $exceptionMessage;
470
471
            public function __construct(string $exceptionMessage)
472
            {
473
                $this->exceptionMessage = $exceptionMessage;
474
            }
475
476
            public function createFromFormat(string $spec, string $format, $timeZone = null): \DateTimeInterface
477
            {
478
                throw new DateTimeFactoryException($this->exceptionMessage);
479
            }
480
481
            public function createDateTimeZone(string $spec): \DateTimeZone
482
            {
483
                return new \DateTimeZone($spec);
484
            }
485
486
            public function createDateTime(string $spec = null, $timeZone = null): \DateTimeInterface
487
            {
488
                /** @phpstan-ignore-next-line */
489
                return new \DateTime($spec, $timeZone);
490
            }
491
        };
492
    }
493
}
494