Passed
Pull Request — master (#7)
by Alex
01:51
created

DateFactoryTest.php$1 ➔ createDateTime()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 2
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
        $dateTimeFactory = new class implements DateTimeFactoryInterface {
65
            public function createFromFormat(string $spec, string $format, $timeZone = null): \DateTimeInterface
66
            {
67
            }
1 ignored issue
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return DateTimeInterface. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
68
69
            public function createDateTimeZone(string $spec): \DateTimeZone
70
            {
71
            }
1 ignored issue
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return DateTimeZone. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
72
73
            public function createDateTime(string $spec = null, $timeZone = null): \DateTimeInterface
74
            {
75
                throw new DateTimeFactoryException(
76
                    sprintf(
77
                        'Failed to create a valid \DateTime instance using \'%s\': %s',
78
                        $spec,
79
                        $timeZone
80
                    )
81
                );
82
            }
83
        };
84
85
        $factory = new DateFactory($dateTimeFactory, $this->dateIntervalFactory);
86
87
        $spec = 'foo';
88
        $timeZone = null;
89
90
        $this->expectException(DateFactoryException::class);
91
        $this->expectExceptionMessage(
92
            sprintf('Failed to create a valid \DateTime instance using \'%s\'', $spec)
93
        );
94
95
        $factory->createDateTime($spec, $timeZone);
96
    }
97
98
    /**
99
     * Ensure that calls to createDateTime() will return the valid configured \DateTime instance.
100
     *
101
     * @param string|null               $spec     The date and time specification.
102
     * @param \DateTimeZone|string|null $timeZone The optional date time zone to test.
103
     *
104
     * @dataProvider getCreateDateTimeData
105
     *
106
     * @throws DateFactoryException
107
     */
108
    public function testCreateDateTime(?string $spec, $timeZone = null): void
109
    {
110
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
111
112
        $expectedDateTime = new \DateTime(
113
            $spec ?? 'now',
114
            is_string($timeZone) ? new \DateTimeZone($timeZone) : $timeZone
115
        );
116
117
        $this->dateTimeFactory->expects($this->once())
118
            ->method('createDateTime')
119
            ->with($spec, $timeZone)
120
            ->willReturn($expectedDateTime);
121
122
        $dateTime = $factory->createDateTime($spec, $timeZone);
123
124
        $this->assertSame($expectedDateTime, $dateTime);
125
    }
126
127
    /**
128
     * @return array
129
     */
130
    public function getCreateDateTimeData(): array
131
    {
132
        return [
133
            [
134
                null,
135
            ],
136
            [
137
                'now',
138
            ],
139
            [
140
                'now',
141
                'Europe/London'
142
            ],
143
            [
144
                '2019-05-14 12:33:00',
145
            ],
146
            [
147
                '2019-08-14 17:34:55',
148
                'UTC',
149
            ],
150
            [
151
                '2020-08-22 14:43:12',
152
                null,
153
            ],
154
            [
155
                '2020-08-22 14:44:37',
156
                new \DateTimeZone('Europe/London'),
157
            ],
158
            [
159
                '2000-01-01',
160
                'Pacific/Nauru'
161
            ],
162
            [
163
                'now',
164
                new \DateTimeZone('America/New_York')
165
            ],
166
        ];
167
    }
168
169
    /**
170
     * Assert that if the \DateTime instance cannot be created a DateTimeFactoryException will be thrown
171
     *
172
     * @param string                    $spec
173
     * @param string                    $format
174
     * @param null|string|\DateTimeZone $timeZone
175
     *
176
     * @dataProvider getCreateFromFormatWillThrowDateFactoryExceptionIfNotAbleToCreateADateTimeData
177
     *
178
     * @throws DateFactoryException
179
     */
180
    public function testCreateFromFormatWillThrowDateFactoryExceptionIfNotAbleToCreateADateTime(
181
        string $spec,
182
        string $format,
183
        $timeZone = null
184
    ): void {
185
        $dateTimeFactory = new class implements DateTimeFactoryInterface {
186
            public function createFromFormat(string $spec, string $format, $timeZone = null): \DateTimeInterface
187
            {
188
                throw new DateTimeFactoryException(
189
                    sprintf(
190
                        'Failed to create a valid \DateTime instance using \'%s\' and format \'%s\'',
191
                        $spec,
192
                        $format
193
                    )
194
                );
195
            }
196
197
            public function createDateTimeZone(string $spec): \DateTimeZone
198
            {
199
            }
1 ignored issue
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return DateTimeZone. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
200
201
            public function createDateTime(string $spec = null, $timeZone = null): \DateTimeInterface
202
            {
203
            }
1 ignored issue
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return DateTimeInterface. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
204
        };
205
206
        $factory = new DateFactory($dateTimeFactory, $this->dateIntervalFactory);
207
208
        $this->expectException(DateFactoryException::class);
209
        $this->expectExceptionMessage(
210
            sprintf(
211
                'Failed to create a valid \DateTime instance using \'%s\' and format \'%s\'',
212
                $spec,
213
                $format
214
            )
215
        );
216
217
        $factory->createFromFormat($spec, $format, $timeZone);
218
    }
219
220
    /**
221
     * @return array
222
     */
223
    public function getCreateFromFormatWillThrowDateFactoryExceptionIfNotAbleToCreateADateTimeData(): array
224
    {
225
        return [
226
            [
227
                'foo',
228
                'xyz',
229
            ],
230
        ];
231
    }
232
233
    /**
234
     * Assert that if providing an invalid $spec to createDateTimeZone() a DateFactoryException is thrown
235
     *
236
     * @param string $spec
237
     *
238
     * @dataProvider getCreateDateTimeZoneWillThrowDateFactoryExceptionIfSpecIsInvalidData
239
     *
240
     * @throws DateFactoryException
241
     */
242
    public function testCreateDateTimeZoneWillThrowDateFactoryExceptionIfSpecIsInvalid(string $spec): void
243
    {
244
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
245
246
        $exceptionCode = 456;
247
        $exceptionMessage = 'This is a test exception message';
248
        $exception = new DateTimeFactoryException($exceptionMessage, $exceptionCode);
249
250
        $this->dateTimeFactory->expects($this->once())
251
            ->method('createDateTimeZone')
252
            ->with($spec)
253
            ->willThrowException($exception);
254
255
        $this->expectException(DateFactoryException::class);
256
        $this->expectExceptionMessage($exceptionMessage);
257
        $this->expectExceptionCode($exceptionCode);
258
259
        $factory->createDateTimeZone($spec);
260
    }
261
262
    /**
263
     * @return array
264
     */
265
    public function getCreateDateTimeZoneWillThrowDateFactoryExceptionIfSpecIsInvalidData(): array
266
    {
267
        return [
268
            [
269
                'foo',
270
            ],
271
            [
272
                '123',
273
            ]
274
        ];
275
    }
276
277
    /**
278
     * @param string $spec
279
     *
280
     * @dataProvider getCreateDateTimeZoneData
281
     *
282
     * @throws DateFactoryException
283
     */
284
    public function testCreateDateTimeZone(string $spec): void
285
    {
286
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
287
288
        $dateTimeZone = new \DateTimeZone($spec);
289
290
        $this->dateTimeFactory->expects($this->once())
291
            ->method('createDateTimeZone')
292
            ->with($spec)
293
            ->willReturn($dateTimeZone);
294
295
        $this->assertSame($dateTimeZone, $factory->createDateTimeZone($spec));
296
    }
297
298
    /**
299
     * @return array
300
     */
301
    public function getCreateDateTimeZoneData(): array
302
    {
303
        return [
304
            [
305
                'UTC',
306
            ],
307
            [
308
                'Europe/London',
309
            ],
310
            [
311
                'Europe/Amsterdam',
312
            ],
313
            [
314
                'Europe/Rome',
315
            ],
316
            [
317
                'Atlantic/Bermuda',
318
            ],
319
            [
320
                'Atlantic/Azores',
321
            ],
322
            [
323
                'Antarctica/DumontDUrville',
324
            ],
325
        ];
326
    }
327
328
    /**
329
     * Assert that calls to creatDateInterval() will return the expected DateInterval instance
330
     *
331
     * @param string $spec
332
     *
333
     * @dataProvider getCreateDateIntervalWillReturnANewDateIntervalToSpecData
334
     *
335
     * @throws DateFactoryException
336
     */
337
    public function testCreateDateIntervalWillReturnANewDateIntervalToSpec(string $spec): void
338
    {
339
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
340
341
        /** @var \DateInterval|MockObject $dateInterval */
342
        $dateInterval = $this->createMock(\DateInterval::class);
343
344
        $this->dateIntervalFactory->expects($this->once())
345
            ->method('createDateInterval')
346
            ->willReturn($dateInterval);
347
348
        $this->assertSame($dateInterval, $factory->createDateInterval($spec));
349
    }
350
351
    /**
352
     * @return array
353
     */
354
    public function getCreateDateIntervalWillReturnANewDateIntervalToSpecData(): array
355
    {
356
        return [
357
            ['P100D'],
358
            ['P4Y1DT9H11M3S'],
359
            ['P2Y4DT6H8M'],
360
            ['P7Y8M'],
361
        ];
362
    }
363
364
    /**
365
     * Assert that an invalid $spec passed to createDateInterval() will raise a DateTimeFactoryException
366
     *
367
     * @throws DateFactoryException
368
     */
369
    public function testDateIntervalWillThrowDateTimeFactoryExceptionIfUnableToCreateADateInterval(): void
370
    {
371
        $spec = 'Hello';
372
373
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
374
375
        $exceptionCode = 123;
376
        $exceptionMessage = 'This is a test exception message';
377
        $exception = new DateIntervalFactoryException($exceptionMessage, $exceptionCode);
378
379
        $this->dateIntervalFactory->expects($this->once())
380
            ->method('createDateInterval')
381
            ->willThrowException($exception);
382
383
        $this->expectDeprecationMessage(DateTimeFactoryException::class);
384
        $this->expectExceptionMessage($exceptionMessage);
385
        $this->expectExceptionCode($exceptionCode);
386
387
        $factory->createDateInterval($spec);
388
    }
389
390
    /**
391
     * Assert that the calls to diff will return a valid DateInterval
392
     *
393
     * @throws DateFactoryException
394
     */
395
    public function testDiffWillReturnDateInterval(): void
396
    {
397
        // @todo Data provider
398
        $origin = new \DateTime();
399
        $target = new \DateTime();
400
        $absolute = false;
401
402
        $dateInterval = $origin->diff($target);
403
404
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
405
406
        $this->dateIntervalFactory->expects($this->once())
407
            ->method('diff')
408
            ->with($origin, $target, $absolute)
409
            ->willReturn($dateInterval);
410
411
        $this->assertSame($dateInterval, $factory->diff($origin, $target, $absolute));
412
    }
413
414
    /**
415
     * Assert that a DateTimeFactoryException is thrown when unable to diff the provided dates
416
     *
417
     * @throws DateFactoryException
418
     */
419
    public function testDateTimeFactoryExceptionWillBeThrownIfDiffFails(): void
420
    {
421
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
422
423
        $origin = new \DateTime();
424
        $target = new \DateTime();
425
426
        $exceptionCode = 123;
427
        $exceptionMessage = 'This is a test exception message';
428
        $exception = new DateIntervalFactoryException($exceptionMessage, $exceptionCode);
429
430
        $this->dateIntervalFactory->expects($this->once())
431
            ->method('diff')
432
            ->with($origin, $target, false)
433
            ->willThrowException($exception);
434
435
        $this->expectException(DateFactoryException::class);
436
        $this->expectExceptionMessage($exceptionMessage);
437
        $this->expectExceptionCode($exceptionCode);
438
439
        $factory->diff($origin, $target);
440
    }
441
}
442