Passed
Pull Request — master (#7)
by Alex
07:24
created

DateFactoryTest.php$1 ➔ createFromFormat()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 3
rs 10
cc 1
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
     * @covers \Arp\DateTime\DateFactory
49
     */
50
    public function testImplementsDateFactoryInterface(): void
51
    {
52
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
53
54
        $this->assertInstanceOf(DateFactoryInterface::class, $factory);
55
    }
56
57
    /**
58
     * Assert that if the \DateTime instance cannot be created a DateTimeFactoryException will be thrown
59
     *
60
     * @throws DateFactoryException
61
     */
62
    public function testCreateDateTimeWillThrowDateFactoryExceptionIfNotAbleToCreateADateTime(): void
63
    {
64
        $spec = 'foo';
65
        $timeZone = null;
66
        $exceptionMessage = sprintf('Failed to create a valid \DateTime instance using \'%s\'', $spec);
67
68
        $dateTimeFactory = $this->createMockForCreateDateTime($exceptionMessage);
69
70
        $factory = new DateFactory($dateTimeFactory, $this->dateIntervalFactory);
71
72
        $this->expectException(DateFactoryException::class);
73
        $this->expectExceptionMessage($exceptionMessage);
74
75
        $factory->createDateTime($spec, $timeZone);
76
    }
77
78
    /**
79
     * Ensure that calls to createDateTime() will return the valid configured \DateTime instance.
80
     *
81
     * @param string|null               $spec     The date and time specification.
82
     * @param \DateTimeZone|string|null $timeZone The optional date time zone to test.
83
     *
84
     * @dataProvider getCreateDateTimeData
85
     *
86
     * @throws DateFactoryException
87
     */
88
    public function testCreateDateTime(?string $spec, $timeZone = null): void
89
    {
90
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
91
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 {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after class keyword; 0 found
Loading history...
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
                return \DateTime::createFromFormat($spec, $format);
1 ignored issue
show
Bug Best Practice introduced by
The expression return DateTime::createFromFormat($spec, $format) could return the type false which is incompatible with the type-hinted return DateTimeInterface. Consider adding an additional type-check to rule them out.
Loading history...
446
            }
447
448
            public function createDateTimeZone(string $spec): \DateTimeZone
449
            {
450
                return new \DateTimeZone($spec);
451
            }
452
453
            public function createDateTime(string $spec = null, $timeZone = null): \DateTimeInterface
454
            {
455
                throw new DateTimeFactoryException($this->exceptionMessage);
456
            }
457
        };
458
    }
459
460
    /**
461
     * @param string $exceptionMessage
462
     *
463
     * @return DateTimeFactoryInterface
464
     */
465
    private function createMockForCreateFromFormat(string $exceptionMessage): DateTimeFactoryInterface
466
    {
467
        return new class($exceptionMessage) implements DateTimeFactoryInterface {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after class keyword; 0 found
Loading history...
468
            private string $exceptionMessage;
469
470
            public function __construct(string $exceptionMessage)
471
            {
472
                $this->exceptionMessage = $exceptionMessage;
473
            }
474
475
            public function createFromFormat(string $spec, string $format, $timeZone = null): \DateTimeInterface
476
            {
477
                throw new DateTimeFactoryException($this->exceptionMessage);
478
            }
479
480
            public function createDateTimeZone(string $spec): \DateTimeZone
481
            {
482
                return new \DateTimeZone($spec);
483
            }
484
485
            public function createDateTime(string $spec = null, $timeZone = null): \DateTimeInterface
486
            {
487
                return new \DateTime($spec, $timeZone);
488
            }
489
        };
490
    }
491
}
492