Passed
Pull Request — master (#3)
by Alex
06:48 queued 04:28
created

ConfigServiceProviderTest   A

Complexity

Total Complexity 4

Size/Duplication

Total Lines 357
Duplicated Lines 0 %

Importance

Changes 7
Bugs 1 Features 0
Metric Value
wmc 4
eloc 148
c 7
b 1
f 0
dl 0
loc 357
rs 10

15 Methods

Rating   Name   Duplication   Size   Complexity  
getRegisterServicesWithFactoriesAndServicesData() 0 50 ?
A testRegisterServicesWillThrowServiceProviderExceptionIfProvidedFactoryIsInvalid() 0 19 1
A testRegisterServicesWillThrowServiceProviderExceptionIfTheArrayServiceIsInvalid() 0 16 1
A testRegisterServicesWillThrowServiceProviderExceptionIfServiceCannotBeRegistered() 0 24 1
B testRegisterServicesWithFactoriesAndServices() 0 35 6
A testRegisterServicesWillThrowServiceProviderExceptionIfTheServiceAliasCannotBeSet() 0 43 1
A hp$0 ➔ create() 0 3 1
A testRegisterServicesWillCatchAdapterExceptionAndRethrowAsServiceProviderException() 0 29 1
A hp$0 ➔ getRegisterServicesWithFactoriesAndServicesData() 0 50 1
A setUp() 0 3 1
A hp$0 ➔ testNotSupportExceptionIsThrownIsStringFactoryIsNotSupportedByTheAdapter() 0 27 1
testNotSupportExceptionIsThrownIsStringFactoryIsNotSupportedByTheAdapter() 0 27 ?
A testImplementsServiceProviderInterface() 0 5 1
testRegistrationOfStringFactories() 0 18 ?
A hp$0 ➔ testRegistrationOfStringFactories() 0 18 1
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
use Psr\Container\ContainerInterface;
19
20
/**
21
 * @covers  \Arp\Container\Provider\ConfigServiceProvider
22
 *
23
 * @author  Alex Patterson <[email protected]>
24
 * @package ArpTest\Container\Provider
25
 */
26
final class ConfigServiceProviderTest extends TestCase
27
{
28
    /**
29
     * @var ContainerAdapterInterface|MockObject
30
     */
31
    private $adapter;
32
33
    /**
34
     * Prepare the test case dependencies
35
     */
36
    public function setUp(): void
37
    {
38
        $this->adapter = $this->createMock(ContainerAdapterInterface::class);
39
    }
40
41
    /**
42
     * Assert that the class implements ServiceProviderInterface.
43
     */
44
    public function testImplementsServiceProviderInterface(): void
45
    {
46
        $serviceProvider = new ConfigServiceProvider([]);
47
48
        $this->assertInstanceOf(ServiceProviderInterface::class, $serviceProvider);
49
    }
50
51
    /**
52
     * Assert that is the adapter raises an exception when executing setService() a ServiceProviderException
53
     * exception is thrown instead
54
     *
55
     * @throws NotSupportedException
56
     * @throws ServiceProviderException
57
     */
58
    public function testRegisterServicesWillThrowServiceProviderExceptionIfServiceCannotBeRegistered(): void
59
    {
60
        $name = 'FooService';
61
        $service = new \stdClass();
62
        $config = [
63
            ConfigServiceProvider::SERVICES => [
64
                $name => $service,
65
            ],
66
        ];
67
68
        $exceptionMessage = 'This is a test exception message from the adapter';
69
        $exceptionCode = 123454;
70
        $exception = new AdapterException($exceptionMessage, $exceptionCode);
71
72
        $this->adapter->expects($this->once())
73
            ->method('setService')
74
            ->with($name, $service)
75
            ->willThrowException($exception);
76
77
        $this->expectException(ServiceProviderException::class);
78
        $this->expectExceptionCode($exceptionCode);
79
        $this->expectExceptionMessage(sprintf('Failed to register service \'%s\': %s', $name, $exceptionMessage));
80
81
        (new ConfigServiceProvider($config))->registerServices($this->adapter);
82
    }
83
84
    /**
85
     * Assert that invalid factories will raise a ServiceProviderException
86
     *
87
     * @throws ServiceProviderException
88
     */
89
    public function testRegisterServicesWillThrowServiceProviderExceptionIfProvidedFactoryIsInvalid(): void
90
    {
91
        $serviceName = 'FooService';
92
        $serviceFactory = false; // this is our invalid factory
93
94
        $serviceProvider = new ConfigServiceProvider(
95
            [
96
                'factories' => [
97
                    $serviceName => $serviceFactory,
98
                ],
99
            ]
100
        );
101
102
        $this->expectException(ServiceProviderException::class);
103
        $this->expectExceptionMessage(
104
            sprintf('Failed to register service \'%s\': The factory provided is not callable', $serviceName)
105
        );
106
107
        $serviceProvider->registerServices($this->adapter);
108
    }
109
110
    /**
111
     * Assert that AdapterException thrown from the adapter are caught and rethrown as ServiceProviderException.
112
     *
113
     * @throws ServiceProviderException
114
     */
115
    public function testRegisterServicesWillCatchAdapterExceptionAndRethrowAsServiceProviderException(): void
116
    {
117
        $serviceName = 'Foo';
118
        $serviceFactory = static function (): \stdClass {
119
            return new \stdClass();
120
        };
121
122
        $config = [
123
            'factories' => [
124
                $serviceName => $serviceFactory,
125
            ],
126
        ];
127
128
        $exceptionMessage = 'This is a test exception message';
129
        $exceptionCode = 3456;
130
        $exception = new AdapterException($exceptionMessage, $exceptionCode);
131
132
        $this->adapter->expects($this->once())
133
            ->method('setFactory')
134
            ->with($serviceName, $serviceFactory)
135
            ->willThrowException($exception);
136
137
        $this->expectException(ServiceProviderException::class);
138
        $this->expectExceptionCode($exceptionCode);
139
        $this->expectExceptionMessage(
140
            sprintf('Failed to register service \'%s\': %s', $serviceName, $exceptionMessage),
141
        );
142
143
        (new ConfigServiceProvider($config))->registerServices($this->adapter);
144
    }
145
146
    /**
147
     * @throws NotSupportedException
148
     * @throws ServiceProviderException
149
     */
150
    public function testRegisterServicesWillThrowServiceProviderExceptionIfTheServiceAliasCannotBeSet(): void
151
    {
152
        $service = new \stdClass();
153
        $serviceName = 'FooService';
154
        $aliasName = 'FooAlias';
155
156
        $config = [
157
            'services' => [
158
                $serviceName => $service,
159
            ],
160
            'aliases'  => [
161
                $aliasName => $serviceName,
162
            ],
163
        ];
164
165
        $exceptionMessage = 'Test exception message';
166
        $exceptionCode = 12345;
167
        $exception = new AdapterException($exceptionMessage, $exceptionCode);
168
169
        /** @var AliasAwareInterface|MockObject $adapter */
170
        $adapter = $this->getMockForAbstractClass(AliasAwareInterface::class);
171
172
        $adapter->expects($this->once())
173
            ->method('setService')
174
            ->with($serviceName, $service);
175
176
        $adapter->expects($this->once())
177
            ->method('setAlias')
178
            ->with($aliasName, $serviceName)
179
            ->willThrowException($exception);
180
181
        $this->expectException(ServiceProviderException::class);
182
        $this->expectExceptionCode($exceptionCode);
183
        $this->expectExceptionMessage(
184
            sprintf(
185
                'Failed to register alias \'%s\' for service \'%s\': %s',
186
                $aliasName,
187
                $serviceName,
188
                $exceptionMessage
189
            )
190
        );
191
192
        (new ConfigServiceProvider($config))->registerServices($adapter);
193
    }
194
195
    /**
196
     * @throws NotSupportedException
197
     * @throws ServiceProviderException
198
     */
199
    public function testRegisterServicesWillThrowServiceProviderExceptionIfTheArrayServiceIsInvalid(): void
200
    {
201
        $serviceName = 'FooService';
202
203
        $config = [
204
            'factories' => [
205
                $serviceName => [],
206
            ],
207
        ];
208
209
        $this->expectException(ServiceProviderException::class);
210
        $this->expectExceptionMessage(
211
            sprintf('Failed to register service \'%s\': The provided array configuration is invalid', $serviceName)
212
        );
213
214
        (new ConfigServiceProvider($config))->registerServices($this->adapter);
215
    }
216
217
    /**
218
     * Assert that register services will correctly register the provided services and factories defined in $config.
219
     *
220
     * @param array $config The services that should be set
221
     *
222
     * @dataProvider getRegisterServicesWithFactoriesAndServicesData
223
     *
224
     * @throws ServiceProviderException
225
     */
226
    public function testRegisterServicesWithFactoriesAndServices(array $config): void
227
    {
228
        $serviceProvider = new ConfigServiceProvider($config);
229
230
        $factories = $config[ConfigServiceProvider::FACTORIES] ?? [];
231
        $services = $config[ConfigServiceProvider::SERVICES] ?? [];
232
233
        $setFactoryArgs = $setServiceArgs = [];
234
235
        foreach ($factories as $name => $factory) {
236
            if (is_array($factory)) {
237
                $methodName = $factory[1] ?? '__invoke';
238
                $factory = $factory[0] ?? null;
239
240
                if (!is_callable($factory) && !$factory instanceof \Closure) {
241
                    $factory = [$factory, $methodName];
242
                }
243
            }
244
245
            $setFactoryArgs[] = [$name, $factory];
246
        }
247
248
        foreach ($services as $name => $service) {
249
            $setServiceArgs[] = [$name, $service];
250
        }
251
252
        $this->adapter->expects($this->exactly(count($setFactoryArgs)))
253
            ->method('setFactory')
254
            ->withConsecutive(...$setFactoryArgs);
255
256
        $this->adapter->expects($this->exactly(count($setServiceArgs)))
257
            ->method('setService')
258
            ->withConsecutive(...$setServiceArgs);
259
260
        $serviceProvider->registerServices($this->adapter);
261
    }
262
263
    /**
264
     * @return array
265
     */
266
    public function getRegisterServicesWithFactoriesAndServicesData(): array
267
    {
268
        return [
269
            [
270
                [], // empty config test
271
            ],
272
273
            [
274
                [
275
                    ConfigServiceProvider::FACTORIES => [
276
                        'FooService' => static function () {
277
                            return 'Hi';
278
                        },
279
                    ],
280
                ],
281
            ],
282
283
            [
284
                [
285
                    'services' => [
286
                        'FooService' => new \stdClass(),
287
                        'BarService' => new \stdClass(),
288
                        'Baz'        => 123,
289
                    ],
290
                ],
291
            ],
292
293
            [
294
                [
295
                    'factories' => [
296
                        'BazStringService' => $this->getMockForAbstractClass(ServiceFactoryInterface::class),
297
                        'Bar'              => static function () {
298
                            return 'Test';
299
                        },
300
                    ],
301
                ],
302
            ],
303
304
            // Array based registration for non callable factory object with custom method name 'create'
305
            [
306
                [
307
                    'factories' => [
308
                        'FooService' => [
309
                            new class {
310
                                public function create(ContainerInterface $container): \stdClass
0 ignored issues
show
Unused Code introduced by
The parameter $container is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

310
                                public function create(/** @scrutinizer ignore-unused */ ContainerInterface $container): \stdClass

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
311
                                {
312
                                    return new \stdClass();
313
                                }
314
                            },
315
                            'create',
316
                        ],
317
                    ],
318
                ],
319
            ],
320
        ];
321
    }
322
323
    /**
324
     * Assert that a NotSupportedException is thrown when passing a string factory configuration to a adapter that
325
     * does not implement FactoryClassAwareInterface
326
     *
327
     * @throws NotSupportedException
328
     * @throws ServiceProviderException
329
     */
330
    public function testNotSupportExceptionIsThrownIsStringFactoryIsNotSupportedByTheAdapter(): void
331
    {
332
        $serviceName = 'Test123';
333
        $factoryName = \stdClass::class;
334
        $config = [
335
            'factories' => [
336
                $serviceName => $factoryName,
337
            ]
338
        ];
339
340
        $serviceProvider = new ConfigServiceProvider($config);
341
342
        $this->expectException(NotSupportedException::class);
343
        $this->expectExceptionMessage(
344
            sprintf(
345
                'Failed to register service \'%s\': The adapter class \'%s\' does not support factory \'%s\' '
346
                . 'which is of type \'string\'.'
347
                . 'The adapter must implement \'%s\' in order to support registration \'string\' factory types',
348
                get_class($this->adapter),
349
                $serviceName,
350
                $factoryName,
351
                FactoryClassAwareInterface::class
352
            )
353
        );
354
355
        // We provide an adapter mock that is doe NOT implement FactoryClassAwareInterface
356
        $serviceProvider->registerServices($this->adapter);
357
    }
358
359
    /**
360
     * Assert that the ServiceProvider supports string factory registration
361
     *
362
     * @throws NotSupportedException
363
     * @throws ServiceProviderException
364
     */
365
    public function testRegistrationOfStringFactories(): void
366
    {
367
        $serviceName = 'Test123';
368
        $factoryName = \stdClass::class;
369
        $config = [
370
            'factories' => [
371
                $serviceName => $factoryName,
372
            ]
373
        ];
374
375
        /** @var FactoryClassAwareInterface|MockObject $adapter */
376
        $adapter = $this->getMockForAbstractClass(FactoryClassAwareInterface::class);
377
        $adapter->expects($this->once())
378
            ->method('setFactoryClass')
379
            ->with($serviceName, $factoryName, null);
380
381
        // We provide an adapter mock that is doe NOT implement FactoryClassAwareInterface
382
        (new ConfigServiceProvider($config))->registerServices($adapter);
383
    }
384
}
385