Passed
Pull Request — master (#7)
by Alex
08:15
created

DateFactoryTest.php$0 ➔ getCreateDateTimeData()   A

Complexity

Conditions 1

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 35
rs 9.36
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\DateTimeFactory
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 calls to creatDateInterval() will return the expected DateInterval instance
235
     *
236
     * @param string $spec
237
     *
238
     * @dataProvider getCreateDateIntervalWillReturnANewDateIntervalToSpecData
239
     *
240
     * @throws DateFactoryException
241
     */
242
    public function testCreateDateIntervalWillReturnANewDateIntervalToSpec(string $spec): void
243
    {
244
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
245
246
        /** @var \DateInterval|MockObject $dateInterval */
247
        $dateInterval = $this->createMock(\DateInterval::class);
248
249
        $this->dateIntervalFactory->expects($this->once())
250
            ->method('createDateInterval')
251
            ->willReturn($dateInterval);
252
253
        $this->assertSame($dateInterval, $factory->createDateInterval($spec));
254
    }
255
256
    /**
257
     * @return array
258
     */
259
    public function getCreateDateIntervalWillReturnANewDateIntervalToSpecData(): array
260
    {
261
        return [
262
            ['P100D'],
263
            ['P4Y1DT9H11M3S'],
264
            ['P2Y4DT6H8M'],
265
            ['P7Y8M'],
266
        ];
267
    }
268
269
    /**
270
     * Assert that an invalid $spec passed to createDateInterval() will raise a DateTimeFactoryException
271
     *
272
     * @throws DateFactoryException
273
     */
274
    public function testDateIntervalWillThrowDateTimeFactoryExceptionIfUnableToCreateADateInterval(): void
275
    {
276
        $spec = 'Hello';
277
278
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
279
280
        $exceptionCode = 123;
281
        $exceptionMessage = 'This is a test exception message';
282
        $exception = new DateIntervalFactoryException($exceptionMessage, $exceptionCode);
283
284
        $this->dateIntervalFactory->expects($this->once())
285
            ->method('createDateInterval')
286
            ->willThrowException($exception);
287
288
        $errorMessage = sprintf(
289
            'Failed to create date interval \'%s\': %s',
290
            $spec,
291
            $exceptionMessage
292
        );
293
294
        $this->expectDeprecationMessage(DateTimeFactoryException::class);
295
        $this->expectExceptionMessage($errorMessage);
296
        $this->expectExceptionCode($exceptionCode);
297
298
        $factory->createDateInterval($spec);
299
    }
300
301
    /**
302
     * Assert that the calls to diff will return a valid DateInterval
303
     *
304
     * @throws DateFactoryException
305
     */
306
    public function testDiffWillReturnDateInterval(): void
307
    {
308
        // @todo Data provider
309
        $origin = new \DateTime();
310
        $target = new \DateTime();
311
        $absolute = false;
312
313
        $dateInterval = $origin->diff($target);
314
315
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
316
317
        $this->dateIntervalFactory->expects($this->once())
318
            ->method('diff')
319
            ->with($origin, $target, $absolute)
320
            ->willReturn($dateInterval);
321
322
        $this->assertSame($dateInterval, $factory->diff($origin, $target, $absolute));
323
    }
324
325
    /**
326
     * Assert that a DateTimeFactoryException is thrown when unable to diff the provided dates
327
     *
328
     * @throws DateFactoryException
329
     */
330
    public function testDateTimeFactoryExceptionWillBeThrownIfDiffFails(): void
331
    {
332
        $factory = new DateFactory($this->dateTimeFactory, $this->dateIntervalFactory);
333
334
        $origin = new \DateTime();
335
        $target = new \DateTime();
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('diff')
343
            ->with($origin, $target, false)
344
            ->willThrowException($exception);
345
346
        $this->expectException(DateFactoryException::class);
347
        $this->expectExceptionMessage(sprintf('Failed to perform date diff: %s', $exceptionMessage));
348
        $this->expectExceptionCode($exceptionCode);
349
350
        $factory->diff($origin, $target);
351
    }
352
}
353