Passed
Pull Request — master (#4)
by Alex
01:54
created

testGetWillThrowContainerExceptionIfAFactoryDependencyCausesACircularCreationDependency()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 18
c 1
b 0
f 0
dl 0
loc 29
rs 9.6666
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ArpTest\Container;
6
7
use Arp\Container\Container;
8
use Arp\Container\Exception\CircularDependencyException;
9
use Arp\Container\Exception\ContainerException;
10
use Arp\Container\Exception\InvalidArgumentException;
11
use Arp\Container\Exception\NotFoundException;
12
use PHPUnit\Framework\TestCase;
13
use Psr\Container\ContainerExceptionInterface;
14
use Psr\Container\ContainerInterface;
15
16
/**
17
 * @covers \Arp\Container\Container
18
 *
19
 * @author  Alex Patterson <[email protected]>
20
 * @package ArpTest\ContainerArray
21
 */
22
final class ContainerTest extends TestCase
23
{
24
    /**
25
     * Assert that the Container implements ContainerInterface.
26
     */
27
    public function testImplementsContainerInterface(): void
28
    {
29
        $container = new Container();
30
31
        $this->assertInstanceOf(ContainerInterface::class, $container);
32
    }
33
34
    /**
35
     * Assert that has() will return true for a service that has been set on the container
36
     *
37
     * @throws ContainerExceptionInterface
38
     */
39
    public function testHasWillAssertBooleanTrueForRegisteredService(): void
40
    {
41
        $container = new Container();
42
43
        $name = \stdClass::class;
44
        $service = new \stdClass();
45
46
        $this->assertFalse($container->has($name));
47
48
        $container->set($name, $service);
49
50
        $this->assertTrue($container->has($name));
51
    }
52
53
    /**
54
     * Assert that has() will return FALSE for a service that has NOT been set on the container
55
     *
56
     * @throws ContainerExceptionInterface
57
     */
58
    public function testHasWillAssertBooleanFalseForNonRegisteredService(): void
59
    {
60
        $container = new Container();
61
62
        $name = \stdClass::class;
63
64
        $this->assertFalse($container->has($name));
65
    }
66
67
    /**
68
     * Assert that a value can be set and returned from the container.
69
     *
70
     * @throws CircularDependencyException
71
     * @throws ContainerException
72
     * @throws NotFoundException
73
     */
74
    public function testGetWillReturnAServiceByName(): void
75
    {
76
        $container = new Container();
77
78
        $name = \stdClass::class;
79
        $service = new \stdClass();
80
81
        $container->set($name, $service);
82
83
        $this->assertSame($service, $container->get($name));
84
    }
85
86
    /**
87
     * Assert that calls to get with a registered service alias will return the named service
88
     *
89
     * @throws ContainerExceptionInterface
90
     */
91
    public function testGetWillReturnAServiceByAliasName(): void
92
    {
93
        $container = new Container();
94
95
        $alias = 'foo';
96
        $name = \stdClass::class;
97
        $service = new \stdClass();
98
99
        $container->set($name, $service);
100
        $container->setAlias($alias, $name);
101
102
        $this->assertSame($service, $container->get($alias));
103
    }
104
105
    /**
106
     * Assert that the container will throw a NotFoundException if the requested service cannot be found.
107
     *
108
     * @throws CircularDependencyException
109
     * @throws ContainerException
110
     * @throws NotFoundException
111
     */
112
    public function testGetWillThrowNotFoundExceptionIfRequestedServiceIsNotRegistered(): void
113
    {
114
        $container = new Container();
115
116
        $name = 'FooService';
117
118
        $this->expectException(NotFoundException::class);
119
        $this->expectExceptionMessage(
120
            sprintf('Service \'%s\' could not be found registered with the container', $name)
121
        );
122
123
        $container->get($name);
124
    }
125
126
    /**
127
     * Assert that a invalid/non-callable factory class will throw a ContainerException.
128
     *
129
     * @throws CircularDependencyException
130
     * @throws ContainerException
131
     * @throws NotFoundException
132
     */
133
    public function testGetWillThrowContainerExceptionIfTheRegisteredFactoryIsNotCallable(): void
134
    {
135
        $container = new Container();
136
137
        $name = 'FooService';
138
        $factoryClassName = \stdClass::class;
139
140
        $container->setFactoryClass($name, $factoryClassName);
141
142
        $this->expectException(ContainerException::class);
143
        $this->expectExceptionMessage(
144
            sprintf(
145
                'Factory \'%s\' registered for service \'%s\', must be callable',
146
                $factoryClassName,
147
                $name
148
            )
149
        );
150
151
        $container->get($name);
152
    }
153
154
    /**
155
     * Assert that we can pass a factory class name string to setFactory() and the service will be registered
156
     *
157
     * @throws ContainerException
158
     * @throws InvalidArgumentException
159
     */
160
    public function testStringFactoryPassedToSetFactoryIsRegistered(): void
161
    {
162
        $container = new Container();
163
164
        $this->assertFalse($container->has('Test'));
165
        $container->setFactory('Test', 'ThisIsAFactoryString');
166
        $this->assertTrue($container->has('Test'));
167
    }
168
169
    /**
170
     * Assert that a InvalidArgumentException is thrown if trying to set a non-string or not callable $factory
171
     * when calling setFactory().
172
     *
173
     * @throws ContainerException
174
     * @throws InvalidArgumentException
175
     */
176
    public function testSetFactoryWithNonStringOrCallableWillThrowInvalidArgumentException(): void
177
    {
178
        $container = new Container();
179
180
        $name = \stdClass::class;
181
        $factory = new \stdClass();
182
183
        $this->expectException(InvalidArgumentException::class);
184
        $this->expectExceptionMessage(
185
            sprintf(
186
                'The \'factory\' argument must be of type \'string\' or \'callable\';'
187
                . '\'%s\' provided for service \'%s\'',
188
                is_object($factory) ? get_class($factory) : gettype($factory),
189
                $name
190
            )
191
        );
192
193
        $container->setFactory($name, $factory);
0 ignored issues
show
Bug introduced by
$factory of type stdClass is incompatible with the type callable|string expected by parameter $factory of Arp\Container\Container::setFactory(). ( Ignorable by Annotation )

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

193
        $container->setFactory($name, /** @scrutinizer ignore-type */ $factory);
Loading history...
194
    }
195
196
    /**
197
     * Assert that circular dependencies between a service name and it's factory are resolved by throwing
198
     * a ContainerException
199
     *
200
     * @throws ContainerExceptionInterface
201
     */
202
    public function testCircularConfigurationDependencyWithFactoryClassNameWillThrowContainerException(): void
203
    {
204
        $name = CallableMock::class;
205
        $factoryClassName = CallableMock::class;
206
207
        $container = new Container();
208
        $container->setFactoryClass($name, $factoryClassName);
209
210
        $this->expectException(ContainerException::class);
211
        $this->expectDeprecationMessage(
212
            sprintf('A circular configuration dependency was detected for service \'%s\'', $name)
213
        );
214
215
        $container->get($name);
216
    }
217
218
    /**
219
     * Assert that the container will throw a ContainerException is the registered factory throws an exception.
220
     *
221
     * @throws ContainerException
222
     */
223
    public function testFactoryCreationErrorWillBeCaughtAndRethrownAsContainerException(): void
224
    {
225
        $container = new Container();
226
227
        $name = 'FooService';
228
        $exceptionMessage = 'This is another test exception message';
229
230
        $factory = static function () use ($exceptionMessage): void {
231
            throw new \RuntimeException($exceptionMessage);
232
        };
233
234
        $container->setFactory($name, $factory);
235
236
        $this->expectException(ContainerException::class);
237
        $this->expectExceptionMessage(
238
            sprintf('The service \'%s\' could not be created: %s', $name, $exceptionMessage)
239
        );
240
241
        $container->get($name);
242
    }
243
244
    /**
245
     * Assert that an unregistered service, which resolves to the name of a valid class, will be created and
246
     * registered with the container. Additional calls to the container's get() method should also return the same
247
     * service
248
     *
249
     * @throws CircularDependencyException
250
     * @throws ContainerException
251
     * @throws NotFoundException
252
     */
253
    public function testGetWillCreateAndReturnUnregisteredServiceIfTheNameResolvesToAValidClassName(): void
254
    {
255
        $container = new Container();
256
257
        $name = \stdClass::class;
258
        $this->assertFalse($container->has($name));
259
        $service = $container->get(\stdClass::class);
260
261
        $this->assertInstanceOf($name, $service);
262
        $this->assertTrue($container->has($name));
263
        $this->assertSame($service, $container->get($name));
264
    }
265
266
    /**
267
     * When creating factories with dependencies, ensure we catch any attempts to load services that depend on each
268
     * other by throwing a ContainerException
269
     *
270
     * @throws CircularDependencyException
271
     * @throws ContainerException
272
     * @throws InvalidArgumentException
273
     * @throws NotFoundException
274
     */
275
    public function testGetWillThrowContainerExceptionIfAFactoryDependencyCausesACircularCreationDependency(): void
276
    {
277
        $container = new Container();
278
279
        $factoryA = static function (ContainerInterface $container) {
280
            $serviceA = new \stdClass();
281
            $serviceA->serviceB = $container->get('ServiceB');
282
            return $serviceA;
283
        };
284
285
        $factoryB = static function (ContainerInterface $container) {
286
            $serviceB = new \stdClass();
287
            $serviceB->serviceA = $container->get('ServiceA');
288
            return $serviceB;
289
        };
290
291
        $container->setFactory('ServiceA', $factoryA);
292
        $container->setFactory('ServiceB', $factoryB);
293
294
        $this->expectException(CircularDependencyException::class);
295
        $this->expectExceptionMessage(
296
            sprintf(
297
                'A circular dependency has been detected for service \'%s\'. The dependency graph includes %s',
298
                'ServiceA',
299
                implode(',', ['ServiceA', 'ServiceB'])
300
            )
301
        );
302
303
        $container->get('ServiceA');
304
    }
305
}
306