Completed
Push — master ( aa9e77...027dae )
by Michael
36:24
created

ContainerSpec   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 337
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 38
c 1
b 0
f 0
lcom 1
cbo 6
dl 0
loc 337
rs 8.3999
1
<?php
2
declare(strict_types = 1);
3
/**
4
 * Contains class ContainerSpec.
5
 *
6
 * PHP version 7.0
7
 *
8
 * LICENSE:
9
 * This file is part of Yet Another Php Eve Api Library also know as Yapeal
10
 * which can be used to access the Eve Online API data and place it into a
11
 * database.
12
 * Copyright (C) 2016 Michael Cummings
13
 *
14
 * This program is free software: you can redistribute it and/or modify it
15
 * under the terms of the GNU Lesser General Public License as published by the
16
 * Free Software Foundation, either version 3 of the License, or (at your
17
 * option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful, but WITHOUT
20
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
21
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
22
 * for more details.
23
 *
24
 * You should have received a copy of the GNU Lesser General Public License
25
 * along with this program. If not, see
26
 * <http://www.gnu.org/licenses/>.
27
 *
28
 * You should be able to find a copy of this license in the LICENSE.md file. A
29
 * copy of the GNU GPL should also be available in the GNU-GPL.md file.
30
 *
31
 * @copyright 2016 Michael Cummings
32
 * @license   http://www.gnu.org/copyleft/lesser.html GNU LGPL
33
 * @author    Michael Cummings <[email protected]>
34
 */
35
namespace Spec\Yapeal\Container;
36
37
use PhpSpec\Exception\Example\SkippingException;
38
use PhpSpec\ObjectBehavior;
39
use Spec\Yapeal\Invokable;
40
use Spec\Yapeal\MockService;
41
use Spec\Yapeal\NonInvokable;
42
43
/**
44
 * Class ContainerSpec
45
 *
46
 * @mixin \Yapeal\Container\Container
47
 *
48
 * @method void during($method, array $params)
49
 * @method void shouldBe($value)
50
 * @method void shouldContain($value)
51
 * @method void shouldHaveKey($key)
52
 * @method void shouldHaveType($value)
53
 * @method void shouldImplement($interface)
54
 * @method void shouldNotEqual($value)
55
 * @method void shouldReturn($result)
56
 */
57
class ContainerSpec extends ObjectBehavior
58
{
59
    public function it_is_initializable()
60
    {
61
        $this->shouldHaveType('Yapeal\Container\Container');
62
    }
63
    public function it_provided_fluent_interface_from_register($provider)
64
    {
65
        /**
66
         * @var \Yapeal\Container\ServiceProviderInterface $provider
67
         */
68
        $provider->beADoubleOf('\Yapeal\Container\ServiceProviderInterface');
69
        $provider->register($this)
70
            ->willReturn();
71
        $this->register($provider)
72
            ->shouldReturn($this);
73
    }
74
    public function it_should_allow_defining_new_service_after_freezing_first()
75
    {
76
        $this['foo'] = function () {
77
            return 'fooValue';
78
        };
79
        $this['foo'];
80
        $this['bar'] = function () {
81
            return 'barValue';
82
        };
83
        $this['bar']->shouldReturn('barValue');
84
    }
85
    public function it_should_allow_extending_non_frozen_service()
86
    {
87
        $this['foo'] = function () {
88
            return 'foo';
89
        };
90
        $this['foo'] = $this->extend('foo',
91
            function ($foo) {
92
                return "$foo.bar";
93
            });
94
        $this['foo'] = $this->extend('foo',
95
            function ($foo) {
96
                return "$foo.baz";
97
            });
98
        $this['foo']->shouldReturn('foo.bar.baz');
99
    }
100
    public function it_should_allow_extending_other_service_after_freezing_first()
101
    {
102
        $this['foo'] = function () {
103
            return 'foo';
104
        };
105
        $this['bar'] = function () {
106
            return 'bar';
107
        };
108
        $this['foo'];
109
        $this['bar'] = $this->extend('bar',
110
            function ($bar, $app) {
111
                return "$bar.baz";
112
            });
113
        $this['bar']->shouldReturn('bar.baz');
114
    }
115
    public function it_should_allow_global_function_names_as_parameter_value()
116
    {
117
        $globals = ['strlen', 'count', 'strtolower'];
118
        foreach ($globals as $global) {
119
            $this['global_function'] = $global;
120
            $this['global_function']->shouldReturn($global);
121
        }
122
    }
123
    public function it_should_allow_removing_frozen_service_and_then_setting_again()
124
    {
125
        $this['foo'] = function () {
126
            return 'fooValue';
127
        };
128
        $this['foo'];
129
        unset($this['foo']);
130
        $this['foo'] = function () {
131
            return 'barValue';
132
        };
133
    }
134
    public function it_should_also_unset_key_when_unsetting_offsets()
135
    {
136
        $this['param'] = 'value';
137
        $this['service'] = function () {
138
            return new MockService();
139
        };
140
        $this->keys()
141
            ->shouldHaveCount(2);
142
        unset($this['param'], $this['service']);
143
        $this->keys()
144
            ->shouldHaveCount(0);
145
    }
146
    public function it_should_have_an_offset_after_one_is_set()
147
    {
148
        $this->keys()
149
            ->shouldHaveCount(0);
150
        $this['param'] = 'value';
151
        $this->keys()
152
            ->shouldContain('param');
153
        $this->keys()
154
            ->shouldHaveCount(1);
155
    }
156
    public function it_should_honor_null_values_in_offset_get()
157
    {
158
        $this['foo'] = null;
159
        $this['foo']->shouldReturn(null);
160
    }
161
    public function it_should_honor_returning_null_values_from_raw()
162
    {
163
        $this['foo'] = null;
164
        $this->raw('foo')
165
            ->shouldReturn(null);
166
    }
167
    public function it_should_initialise_offsets_from_constructor()
168
    {
169
        $params = ['param' => 'value', 'param2' => false];
170
        $this->beConstructedWith($params);
171
        $this->keys()
172
            ->shouldHaveCount(2);
173
        foreach ($params as $param => $value) {
174
            $this[$param]->shouldBe($value);
175
        }
176
    }
177
    public function it_should_not_invoke_protected_services()
178
    {
179
        $services = [
180
            function ($value) {
181
                $service = new MockService();
182
                $service->value = $value;
183
                return $service;
184
            },
185
            new Invokable()
186
        ];
187
        foreach ($services as $service) {
188
            $this['protected'] = $this->protect($service);
189
            $this['protected']->shouldReturn($service);
190
        }
191
    }
192
    public function it_should_pass_container_as_parameter()
193
    {
194
        $this['service'] = function () {
195
            return new MockService();
196
        };
197
        $this['container'] = function ($container) {
198
            return $container;
199
        };
200
        $this['service']->shouldNotEqual($this);
201
        $this['container']->shouldEqual($this);
202
    }
203
    public function it_should_return_different_instances_of_same_type_from_factory()
204
    {
205
        $this['service'] = $this->factory(function () {
206
            return new MockService();
207
        });
208
        $serviceOne = $this['service']->shouldHaveType('Spec\Yapeal\MockService');
209
        $this['service']->shouldNotEqual($serviceOne);
210
    }
211
    public function it_should_return_original_instance_from_raw_when_using_factory()
212
    {
213
        $definition = $this->factory(function () {
214
            return 'foo';
215
        });
216
        $this['service'] = $definition;
217
        $this->raw('service')
218
            ->shouldReturn($definition);
219
        $this['service']->shouldNotReturn($definition);
220
    }
221
    public function it_should_return_same_instance_and_type_as_callable_returns()
222
    {
223
        $this['service'] = function () {
224
            return new MockService();
225
        };
226
        $serviceOne = $this['service']->shouldHaveType('Spec\Yapeal\MockService');
227
        $this['service']->shouldEqual($serviceOne);
228
    }
229
    public function it_should_return_same_type_and_value_for_simple_values()
230
    {
231
        foreach ([true, false, null, 'value', 1, 1.0, ['value']] as $item) {
232
            $this['param'] = $item;
233
            $this['param']->shouldBe($item);
234
        }
235
    }
236
    public function it_should_return_true_from_offset_exists_for_any_set_key()
237
    {
238
        $this['param'] = 'value';
239
        $this['service'] = function () {
240
            return new MockService();
241
        };
242
        $this['null'] = null;
243
        $this->offsetExists('param')
244
            ->shouldReturn(true);
245
        $this->offsetExists('param')
246
            ->shouldReturn(isset($this['param']));
247
        $this->offsetExists('service')
248
            ->shouldReturn(true);
249
        $this->offsetExists('service')
250
            ->shouldReturn(isset($this['service']));
251
        $this->offsetExists('null')
252
            ->shouldReturn(true);
253
        $this->offsetExists('null')
254
            ->shouldReturn(isset($this['null']));
255
        $this->offsetExists('non_existent')
256
            ->shouldNotReturn(true);
257
        $this->offsetExists('non_existent')
258
            ->shouldNotReturn(isset($this['non_existent']));
259
    }
260
    public function it_should_treat_invokable_object_like_callable()
261
    {
262
        $this['invokable'] = new Invokable();
263
        $invoked = $this['invokable']->shouldHaveType('Spec\Yapeal\MockService');
264
        $this['invokable']->shouldReturn($invoked);
265
    }
266
    public function it_should_treat_non_invokable_object_like_parameter()
267
    {
268
        $this['non_invokable'] = new NonInvokable();
269
        $this['non_invokable']->shouldHaveType('Spec\Yapeal\NonInvokable');
270
    }
271
    public function it_throws_exception_for_non_existent_extend_offset()
272
    {
273
        $id = 'param';
274
        $mess = sprintf('Identifier "%s" is not defined.', $id);
275
        $this->shouldThrow(new \InvalidArgumentException($mess))
276
            ->during('extend',
277
                [
278
                    $id,
279
                    function () {
280
                    }
281
                ]);
282
    }
283
    public function it_throws_exception_for_non_existent_get_offset()
284
    {
285
        $id = 'param';
286
        $mess = sprintf('Identifier "%s" is not defined.', $id);
287
        $this->shouldThrow(new \InvalidArgumentException($mess))
288
            ->during('offsetGet', [$id]);
289
    }
290
    public function it_throws_exception_for_non_existent_raw_offset()
291
    {
292
        $id = 'param';
293
        $mess = sprintf('Identifier "%s" is not defined.', $id);
294
        $this->shouldThrow(new \InvalidArgumentException($mess))
295
            ->during('raw', [$id]);
296
    }
297
    public function it_throws_exception_when_extend_is_given_non_invokable()
298
    {
299
        if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
300
            throw new SkippingException('Unneeded on PHP 7.x or higher caught by TypeError');
301
        }
302
        $this['foo'] = function () {
303
            return 'foo';
304
        };
305
        $this->shouldThrow(new \InvalidArgumentException('Extension service definition is not a Closure or invokable object.'))
306
            ->during('extend', ['foo', new NonInvokable()]);
307
    }
308
    public function it_throws_exception_when_factory_is_given_non_invokable()
309
    {
310
        if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
311
            throw new SkippingException('Unneeded on PHP 7.x or higher caught by TypeError');
312
        }
313
        $this['service'] = $this->shouldThrow(new \InvalidArgumentException('Service definition is not a Closure or invokable object.'))
314
            ->during('factory', [123]);
315
        $this['service'] = $this->shouldThrow(new \InvalidArgumentException('Service definition is not a Closure or invokable object.'))
316
            ->during('factory', [new NonInvokable()]);
317
    }
318
    public function it_throws_exception_when_protect_is_given_non_invokable()
319
    {
320
        if (version_compare(PHP_VERSION, '7.0.0', '>=')) {
321
            throw new SkippingException('Unneeded on PHP 7.x or higher caught by TypeError');
322
        }
323
        $this->shouldThrow(new \InvalidArgumentException('Callable is not a Closure or invokable object.'))
324
            ->during('protect', [new NonInvokable()]);
325
    }
326
    public function it_throws_exception_when_trying_to_extend_non_invokable()
327
    {
328
        $this['param'] = 123;
329
        $this['non_invokable'] = new NonInvokable();
330
        $this['param'] = $this->shouldThrow(new \InvalidArgumentException('Identifier "param" does not contain an object definition.'))
331
            ->during('extend',
332
                [
333
                    'param',
334
                    function () {
335
                    }
336
                ]);
337
        $this['non_invokable'] = $this->shouldThrow(new \InvalidArgumentException('Identifier "non_invokable" does not contain an object definition.'))
338
            ->during('extend',
339
                [
340
                    'non_invokable',
341
                    function () {
342
                    }
343
                ]);
344
    }
345
    public function it_throws_exception_when_trying_to_extending_frozen_service()
346
    {
347
        $this['foo'] = function () {
348
            return 'foo';
349
        };
350
        $this['foo'] = $this->extend('foo',
351
            function ($foo) {
352
                return "$foo.bar";
353
            });
354
        $this['foo']->shouldReturn('foo.bar');
355
        $this->shouldThrow(new \InvalidArgumentException('Identifier "foo" does not contain an object definition.'))
356
            ->during('extend',
357
                [
358
                    'foo',
359
                    function ($foo) {
360
                        return "$foo.baz";
361
                    }
362
                ]);
363
    }
364
    public function it_throws_exception_when_trying_to_get_offset_after_they_have_been_unset()
365
    {
366
        $this['param'] = 'value';
367
        $this['service'] = function () {
368
            return new MockService();
369
        };
370
        $this->keys()
371
            ->shouldHaveCount(2);
372
        unset($this['param'], $this['service']);
373
        $this->shouldThrow(new \InvalidArgumentException('Identifier "param" is not defined.'))
374
            ->during('offsetGet', ['param']);
375
        $this->shouldThrow(new \InvalidArgumentException('Identifier "service" is not defined.'))
376
            ->during('offsetGet', ['service']);
377
    }
378
    public function it_throws_exception_when_trying_to_over_write_frozen_service()
379
    {
380
        $this['foo'] = function () {
381
            return 'foo';
382
        };
383
        $this['foo'];
384
        $this->shouldThrow(new \RuntimeException('Cannot override frozen service "foo".'))
385
            ->during('offsetSet',
386
                [
387
                    'foo',
388
                    function () {
389
                        return 'bar';
390
                    }
391
                ]);
392
    }
393
}
394