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

ContainerTest::testGetWillReturnAServiceByName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 10
rs 10
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 circular dependencies between a service name and it's factory are resolved by throwing
156
     * a ContainerException
157
     *
158
     * @throws ContainerExceptionInterface
159
     */
160
    public function testCircularConfigurationDependencyWithFactoryClassNameWillThrowContainerException(): void
161
    {
162
        $name = CallableMock::class;
163
        $factoryClassName = CallableMock::class;
164
165
        $container = new Container();
166
        $container->setFactoryClass($name, $factoryClassName);
167
168
        $this->expectException(ContainerException::class);
169
        $this->expectDeprecationMessage(
170
            sprintf('A circular configuration dependency was detected for service \'%s\'', $name)
171
        );
172
173
        $container->get($name);
174
    }
175
176
    /**
177
     * Assert that the container will throw a ContainerException is the registered factory throws an exception.
178
     *
179
     * @throws ContainerException
180
     */
181
    public function testFactoryCreationErrorWillBeCaughtAndRethrownAsContainerException(): void
182
    {
183
        $container = new Container();
184
185
        $name = 'FooService';
186
        $exceptionMessage = 'This is another test exception message';
187
188
        $factory = static function () use ($exceptionMessage): void {
189
            throw new \RuntimeException($exceptionMessage);
190
        };
191
192
        $container->setFactory($name, $factory);
193
194
        $this->expectException(ContainerException::class);
195
        $this->expectExceptionMessage(
196
            sprintf('The service \'%s\' could not be created: %s', $name, $exceptionMessage)
197
        );
198
199
        $container->get($name);
200
    }
201
202
    /**
203
     * Assert that an unregistered service, which resolves to the name of a valid class, will be created and
204
     * registered with the container. Additional calls to the container's get() method should also return the same
205
     * service
206
     *
207
     * @throws CircularDependencyException
208
     * @throws ContainerException
209
     * @throws NotFoundException
210
     */
211
    public function testGetWillCreateAndReturnUnregisteredServiceIfTheNameResolvesToAValidClassName(): void
212
    {
213
        $container = new Container();
214
215
        $name = \stdClass::class;
216
        $this->assertFalse($container->has($name));
217
        $service = $container->get(\stdClass::class);
218
219
        $this->assertInstanceOf($name, $service);
220
        $this->assertTrue($container->has($name));
221
        $this->assertSame($service, $container->get($name));
222
    }
223
224
    /**
225
     * When creating factories with dependencies, ensure we catch any attempts to load services that depend on each
226
     * other by throwing a ContainerException
227
     *
228
     * @throws CircularDependencyException
229
     * @throws ContainerException
230
     * @throws InvalidArgumentException
231
     * @throws NotFoundException
232
     */
233
    public function testGetWillThrowContainerExceptionIfAFactoryDependencyCausesACircularCreationDependency(): void
234
    {
235
        $container = new Container();
236
237
        $factoryA = static function (ContainerInterface $container) {
238
            $serviceA = new \stdClass();
239
            $serviceA->serviceB = $container->get('ServiceB');
240
            return $serviceA;
241
        };
242
243
        $factoryB = static function (ContainerInterface $container) {
244
            $serviceB = new \stdClass();
245
            $serviceB->serviceA = $container->get('ServiceA');
246
            return $serviceB;
247
        };
248
249
        $container->setFactory('ServiceA', $factoryA);
250
        $container->setFactory('ServiceB', $factoryB);
251
252
        $this->expectException(CircularDependencyException::class);
253
        $this->expectExceptionMessage(
254
            sprintf(
255
                'A circular dependency has been detected for service \'%s\'. The dependency graph includes %s',
256
                'ServiceA',
257
                implode(',', ['ServiceA', 'ServiceB'])
258
            )
259
        );
260
261
        $container->get('ServiceA');
262
    }
263
}
264