Passed
Push — master ( 261e75...7da9e7 )
by Alex
36s queued 11s
created

  A

Complexity

Total Complexity 1

Size/Duplication

Total Lines 4
Duplicated Lines 0 %

Importance

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