Passed
Pull Request — master (#3)
by Alex
02:12
created

php$0 ➔ getRegisterServicesWithFactoriesAndServicesData()   B

Complexity

Conditions 1

Size

Total Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 63
rs 8.8072
cc 1

1 Method

Rating   Name   Duplication   Size   Complexity  
A ConfigServiceProviderTest.php$0 ➔ create() 0 3 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace ArpTest\Container\Provider;
6
7
use Arp\Container\Adapter\AliasAwareInterface;
8
use Arp\Container\Adapter\ContainerAdapterInterface;
9
use Arp\Container\Adapter\Exception\AdapterException;
10
use Arp\Container\Adapter\FactoryClassAwareInterface;
11
use Arp\Container\Factory\ServiceFactoryInterface;
12
use Arp\Container\Provider\ConfigServiceProvider;
13
use Arp\Container\Provider\Exception\NotSupportedException;
14
use Arp\Container\Provider\Exception\ServiceProviderException;
15
use Arp\Container\Provider\ServiceProviderInterface;
16
use PHPUnit\Framework\MockObject\MockObject;
17
use PHPUnit\Framework\TestCase;
18
19
/**
20
 * @covers  \Arp\Container\Provider\ConfigServiceProvider
21
 *
22
 * @author  Alex Patterson <[email protected]>
23
 * @package ArpTest\Container\Provider
24
 */
25
final class ConfigServiceProviderTest extends TestCase
26
{
27
    /**
28
     * @var ContainerAdapterInterface|MockObject
29
     */
30
    private $adapter;
31
32
    /**
33
     * Prepare the test case dependencies
34
     */
35
    public function setUp(): void
36
    {
37
        $this->adapter = $this->createMock(ContainerAdapterInterface::class);
38
    }
39
40
    /**
41
     * Assert that the class implements ServiceProviderInterface.
42
     */
43
    public function testImplementsServiceProviderInterface(): void
44
    {
45
        $serviceProvider = new ConfigServiceProvider([]);
46
47
        $this->assertInstanceOf(ServiceProviderInterface::class, $serviceProvider);
48
    }
49
50
    /**
51
     * Assert that is the adapter raises an exception when executing setService() a ServiceProviderException
52
     * exception is thrown instead
53
     *
54
     * @throws NotSupportedException
55
     * @throws ServiceProviderException
56
     */
57
    public function testRegisterServicesWillThrowServiceProviderExceptionIfServiceCannotBeRegistered(): void
58
    {
59
        $name = 'FooService';
60
        $service = new \stdClass();
61
        $config = [
62
            ConfigServiceProvider::SERVICES => [
63
                $name => $service,
64
            ],
65
        ];
66
67
        $exceptionMessage = 'This is a test exception message from the adapter';
68
        $exceptionCode = 123454;
69
        $exception = new AdapterException($exceptionMessage, $exceptionCode);
70
71
        $this->adapter->expects($this->once())
72
            ->method('setService')
73
            ->with($name, $service)
74
            ->willThrowException($exception);
75
76
        $this->expectException(ServiceProviderException::class);
77
        $this->expectExceptionCode($exceptionCode);
78
        $this->expectExceptionMessage(sprintf('Failed to register service \'%s\': %s', $name, $exceptionMessage));
79
80
        (new ConfigServiceProvider($config))->registerServices($this->adapter);
81
    }
82
83
    /**
84
     * Assert that invalid factories will raise a ServiceProviderException
85
     *
86
     * @throws ServiceProviderException
87
     */
88
    public function testRegisterServicesWillThrowServiceProviderExceptionIfProvidedFactoryIsInvalid(): void
89
    {
90
        $serviceName = 'FooService';
91
        $serviceFactory = false; // this is our invalid factory
92
93
        $serviceProvider = new ConfigServiceProvider(
94
            [
95
                'factories' => [
96
                    $serviceName => $serviceFactory,
97
                ],
98
            ]
99
        );
100
101
        $this->expectException(ServiceProviderException::class);
102
        $this->expectExceptionMessage(
103
            sprintf('Failed to register service \'%s\': The factory provided is not callable', $serviceName)
104
        );
105
106
        $serviceProvider->registerServices($this->adapter);
107
    }
108
109
    /**
110
     * Assert that AdapterException thrown from the adapter are caught and rethrown as ServiceProviderException.
111
     *
112
     * @throws ServiceProviderException
113
     */
114
    public function testRegisterServicesWillCatchAdapterExceptionAndRethrowAsServiceProviderException(): void
115
    {
116
        $serviceName = 'Foo';
117
        $serviceFactory = static function (): \stdClass {
118
            return new \stdClass();
119
        };
120
121
        $config = [
122
            'factories' => [
123
                $serviceName => $serviceFactory,
124
            ],
125
        ];
126
127
        $exceptionMessage = 'This is a test exception message';
128
        $exceptionCode = 3456;
129
        $exception = new AdapterException($exceptionMessage, $exceptionCode);
130
131
        $this->adapter->expects($this->once())
132
            ->method('setFactory')
133
            ->with($serviceName, $serviceFactory)
134
            ->willThrowException($exception);
135
136
        $this->expectException(ServiceProviderException::class);
137
        $this->expectExceptionCode($exceptionCode);
138
        $this->expectExceptionMessage(
139
            sprintf('Failed to register service \'%s\': %s', $serviceName, $exceptionMessage),
140
        );
141
142
        (new ConfigServiceProvider($config))->registerServices($this->adapter);
143
    }
144
145
    /**
146
     * @throws NotSupportedException
147
     * @throws ServiceProviderException
148
     */
149
    public function testRegisterServicesWillThrowServiceProviderExceptionIfTheServiceAliasCannotBeSet(): void
150
    {
151
        $service = new \stdClass();
152
        $serviceName = 'FooService';
153
        $aliasName = 'FooAlias';
154
155
        $config = [
156
            'services' => [
157
                $serviceName => $service,
158
            ],
159
            'aliases'  => [
160
                $aliasName => $serviceName,
161
            ],
162
        ];
163
164
        $exceptionMessage = 'Test exception message';
165
        $exceptionCode = 12345;
166
        $exception = new AdapterException($exceptionMessage, $exceptionCode);
167
168
        /** @var AliasAwareInterface|MockObject $adapter */
169
        $adapter = $this->getMockForAbstractClass(AliasAwareInterface::class);
170
171
        $adapter->expects($this->once())
172
            ->method('setService')
173
            ->with($serviceName, $service);
174
175
        $adapter->expects($this->once())
176
            ->method('setAlias')
177
            ->with($aliasName, $serviceName)
178
            ->willThrowException($exception);
179
180
        $this->expectException(ServiceProviderException::class);
181
        $this->expectExceptionCode($exceptionCode);
182
        $this->expectExceptionMessage(
183
            sprintf(
184
                'Failed to register alias \'%s\' for service \'%s\': %s',
185
                $aliasName,
186
                $serviceName,
187
                $exceptionMessage
188
            )
189
        );
190
191
        (new ConfigServiceProvider($config))->registerServices($adapter);
192
    }
193
194
    /**
195
     * @throws NotSupportedException
196
     * @throws ServiceProviderException
197
     */
198
    public function testRegisterServicesWillThrowServiceProviderExceptionIfTheArrayServiceIsInvalid(): void
199
    {
200
        $serviceName = 'FooService';
201
202
        $config = [
203
            'factories' => [
204
                $serviceName => [],
205
            ],
206
        ];
207
208
        $this->expectException(ServiceProviderException::class);
209
        $this->expectExceptionMessage(
210
            sprintf('Failed to register service \'%s\': The provided array configuration is invalid', $serviceName)
211
        );
212
213
        (new ConfigServiceProvider($config))->registerServices($this->adapter);
214
    }
215
216
    /**
217
     * Assert that register services will correctly register the provided services and factories defined in $config.
218
     *
219
     * @param array $config The services that should be set
220
     *
221
     * @dataProvider getRegisterServicesWithFactoriesAndServicesData
222
     *
223
     * @throws ServiceProviderException
224
     */
225
    public function testRegisterServicesWithFactoriesAndServices(array $config): void
226
    {
227
        $serviceProvider = new ConfigServiceProvider($config);
228
229
        $factories = $config[ConfigServiceProvider::FACTORIES] ?? [];
230
        $services = $config[ConfigServiceProvider::SERVICES] ?? [];
231
232
        $setFactoryArgs = $setServiceArgs = $setFactoryClassArgs = [];
233
234
        /** @var FactoryClassAwareInterface|MockObject $adapter */
235
        $adapter = $this->getMockForAbstractClass(FactoryClassAwareInterface::class);
236
        foreach ($factories as $name => $factory) {
237
            $methodName = null;
238
239
            if (is_array($factory)) {
240
                $methodName = $factory[1] ?? null;
241
                $factory = $factory[0] ?? null;
242
243
                if (is_string($factory)) {
244
                    $setFactoryClassArgs[] = [$name, $factory, $methodName];
245
                    continue;
246
                }
247
                if (!is_callable($factory) && !$factory instanceof \Closure) {
248
                    $factory = [$factory, $methodName];
249
                }
250
            }
251
252
            if (is_string($factory)) {
253
                $setFactoryClassArgs[] = [$name, $factory, $methodName];
254
                continue;
255
            }
256
            $setFactoryArgs[] = [$name, $factory];
257
        }
258
259
        foreach ($services as $name => $service) {
260
            $setServiceArgs[] = [$name, $service];
261
        }
262
263
        $adapter->expects($this->exactly(count($setFactoryClassArgs)))
264
            ->method('setFactoryClass')
265
            ->withConsecutive(...$setFactoryClassArgs);
266
267
        $adapter->expects($this->exactly(count($setFactoryArgs)))
268
            ->method('setFactory')
269
            ->withConsecutive(...$setFactoryArgs);
270
271
        $adapter->expects($this->exactly(count($setServiceArgs)))
272
            ->method('setService')
273
            ->withConsecutive(...$setServiceArgs);
274
275
        $serviceProvider->registerServices($adapter);
276
    }
277
278
    /**
279
     * @return array
280
     */
281
    public function getRegisterServicesWithFactoriesAndServicesData(): array
282
    {
283
        return [
284
            [
285
                [], // empty config test
286
            ],
287
288
            [
289
                [
290
                    ConfigServiceProvider::FACTORIES => [
291
                        'FooService' => static function () {
292
                            return 'Hi';
293
                        },
294
                    ],
295
                ],
296
            ],
297
298
            [
299
                [
300
                    'services' => [
301
                        'FooService' => new \stdClass(),
302
                        'BarService' => new \stdClass(),
303
                        'Baz'        => 123,
304
                    ],
305
                ],
306
            ],
307
308
            [
309
                [
310
                    'factories' => [
311
                        'BazStringService' => $this->getMockForAbstractClass(ServiceFactoryInterface::class),
312
                        'Bar'              => static function () {
313
                            return 'Test';
314
                        },
315
                    ],
316
                ],
317
            ],
318
319
            // Array based registration for non callable factory object with custom method name 'create'
320
            [
321
                [
322
                    'factories' => [
323
                        'FooService' => [
324
                            new class {
325
                                public function create(): \stdClass
326
                                {
327
                                    return new \stdClass();
328
                                }
329
                            },
330
                            'create',
331
                        ],
332
                    ],
333
                ],
334
            ],
335
336
            // String and array based registration
337
            [
338
                [
339
                    'factories' => [
340
                        'FooService' => 'StringFactoryName',
341
                        'BazService' => ['BazServiceFactory'],
342
                        'BarService' => ['StringBarServiceFactory', 'methodNameThatWillBeCalled'],
343
                        'ZapService' => ['ZapServiceFactory', '__invoke'],
344
                    ],
345
                ]
346
            ]
347
        ];
348
    }
349
350
    /**
351
     * Assert that a NotSupportedException is thrown when passing a string factory configuration to a adapter that
352
     * does not implement FactoryClassAwareInterface
353
     *
354
     * @throws NotSupportedException
355
     * @throws ServiceProviderException
356
     */
357
    public function testNotSupportExceptionIsThrownIsStringFactoryIsNotSupportedByTheAdapter(): void
358
    {
359
        $serviceName = 'Test123';
360
        $factoryName = \stdClass::class;
361
        $config = [
362
            'factories' => [
363
                $serviceName => $factoryName,
364
            ]
365
        ];
366
367
        $serviceProvider = new ConfigServiceProvider($config);
368
369
        $this->expectException(NotSupportedException::class);
370
        $this->expectExceptionMessage(
371
            sprintf(
372
                'Failed to register service \'%s\': The adapter class \'%s\' does not support factory \'%s\' '
373
                . 'which is of type \'string\'.'
374
                . 'The adapter must implement \'%s\' in order to support registration \'string\' factory types',
375
                get_class($this->adapter),
376
                $serviceName,
377
                $factoryName,
378
                FactoryClassAwareInterface::class
379
            )
380
        );
381
382
        // We provide an adapter mock that is doe NOT implement FactoryClassAwareInterface
383
        $serviceProvider->registerServices($this->adapter);
384
    }
385
386
    /**
387
     * Assert that a ServiceProviderException is thrown when the provider is unable to register a factory class
388
     *
389
     * @throws NotSupportedException
390
     * @throws ServiceProviderException
391
     */
392
    public function testStringFactoryRegistrationFailureWillThrowServiceProviderException(): void
393
    {
394
        $serviceName = 'FooService';
395
        $factoryName = 'FooServiceFactory';
396
        $factoryMethodName = null;
397
398
        $config = [
399
            'factories' => [
400
                $serviceName => $factoryName,
401
            ]
402
        ];
403
404
        /** @var FactoryClassAwareInterface|MockObject $adapter */
405
        $adapter = $this->getMockForAbstractClass(FactoryClassAwareInterface::class);
406
407
        $exceptionMessage = 'The exception message test string';
408
        $exceptionCode = 12345;
409
        $exception = new AdapterException($exceptionMessage, $exceptionCode);
410
411
        $this->expectException(ServiceProviderException::class);
412
        $this->expectExceptionCode($exceptionCode);
413
        $this->expectExceptionMessage(
414
            sprintf(
415
                'Failed to register service \'%s\' with adapter \'%s\' using factory class \'%s\': %s',
416
                $serviceName,
417
                get_class($adapter),
418
                $factoryName,
419
                $exceptionMessage
420
            )
421
        );
422
423
        $adapter->expects($this->once())
424
            ->method('setFactoryClass')
425
            ->with($serviceName, $factoryName, $factoryMethodName)
426
            ->willThrowException($exception);
427
428
        (new ConfigServiceProvider($config))->registerServices($adapter);
429
    }
430
431
    /**
432
     * Assert that the ServiceProvider supports string factory registration
433
     *
434
     * @throws NotSupportedException
435
     * @throws ServiceProviderException
436
     */
437
    public function testRegistrationOfStringFactories(): void
438
    {
439
        $serviceName = 'Test123';
440
        $factoryName = \stdClass::class;
441
        $config = [
442
            'factories' => [
443
                $serviceName => $factoryName,
444
            ]
445
        ];
446
447
        /** @var FactoryClassAwareInterface|MockObject $adapter */
448
        $adapter = $this->getMockForAbstractClass(FactoryClassAwareInterface::class);
449
        $adapter->expects($this->once())
450
            ->method('setFactoryClass')
451
            ->with($serviceName, $factoryName, null);
452
453
        // We provide an adapter mock that is doe NOT implement FactoryClassAwareInterface
454
        (new ConfigServiceProvider($config))->registerServices($adapter);
455
    }
456
}
457