Passed
Branch master (e8fd46)
by Alexey
03:15
created

ContainerTest::savesReflectionResolveResult()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 39
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 23
nc 1
nop 0
dl 0
loc 39
rs 8.8571
c 0
b 0
f 0
1
<?php
2
3
use PHPUnit\Framework\TestCase;
4
use Venta\Container\Container;
5
use Venta\Contracts\Container\ArgumentResolver;
6
use Venta\Contracts\Container\ObjectInflector;
7
8
/**
9
 * Class ContainerTest
10
 */
11
class ContainerTest extends TestCase
12
{
13
    public function tearDown()
14
    {
15
        Mockery::close();
16
    }
17
18
    /**
19
     * @test
20
     */
21
    public function canApplyInflectionsOnGet()
22
    {
23
        $container = new Venta\Container\Container;
24
        $container->addInflection(TestClass::class, 'setValue', [42]);
25
        $result = $container->get(TestClass::class);
26
27
        $this->assertSame(42, $result->getValue());
28
    }
29
30
    /**
31
     * @test
32
     */
33
    public function canApplyInflectionsOnManyInstances()
34
    {
35
        $container = new Venta\Container\Container;
36
        $container->addInflection(TestClass::class, 'setValue', [42]);
37
        $test1 = $container->get(TestClass::class);
38
        $test2 = $container->get(TestClass::class);
39
        $test3 = $container->get(TestClass::class);
40
41
        $this->assertSame(42, $test1->getValue());
42
        $this->assertSame(42, $test2->getValue());
43
        $this->assertSame(42, $test3->getValue());
44
    }
45
46
    /**
47
     * @test
48
     */
49
    public function canCallCallableFunctionName()
50
    {
51
        $container = new Venta\Container\Container;
52
        $this->assertInstanceOf(TestClassContract::class, $container->call('createTestClass'));
53
    }
54
55
    /**
56
     * @test
57
     */
58
    public function canCallClassNameMethod()
59
    {
60
        $container = new Venta\Container\Container;
61
        $result = $container->call('TestClassFactory::createAndSetValue');
62
63
        $this->assertInstanceOf(TestClassContract::class, $result);
64
        $this->assertInstanceOf(stdClass::class, $result->getValue());
65
    }
66
67
    /**
68
     * @test
69
     */
70
    public function canCallClassNameMethodFromArray()
71
    {
72
        $container = new Venta\Container\Container;
73
        $result = $container->call(['TestClassFactory', 'createAndSetValue']);
74
75
        $this->assertInstanceOf(TestClassContract::class, $result);
76
        $this->assertInstanceOf(stdClass::class, $result->getValue());
77
    }
78
79
    /**
80
     * @test
81
     */
82
    public function canCallClassNameMethodStatically()
83
    {
84
        $container = new Venta\Container\Container;
85
86
        $this->assertInstanceOf(TestClassContract::class, $container->call('StaticTestFactory::create'));
87
    }
88
89
    /**
90
     * @test
91
     */
92
    public function canCallClosure()
93
    {
94
        $container = new Venta\Container\Container;
95
        $object = new stdClass();
96
        $object->key = 'value';
97
        $container->bindInstance(stdClass::class, $object);
98
        $result = $container->call(function (stdClass $dependency) {
99
            return $dependency->key;
100
        });
101
102
        $this->assertSame('value', $result);
103
    }
104
105
    /**
106
     * @test
107
     */
108
    public function canCallInterfaceMethod()
109
    {
110
        $container = new Venta\Container\Container;
111
        $container->bindInstance(TestClassFactoryContract::class, new TestClassFactory(new stdClass()));
112
113
        $this->assertInstanceOf(TestClassContract::class, $container->call('TestClassFactoryContract::create'));
114
    }
115
116
    /**
117
     * @test
118
     */
119
    public function canCallInvokableClassName()
120
    {
121
        $container = new Venta\Container\Container;
122
        $this->assertInstanceOf(TestClassContract::class, $container->call('TestClassFactory'));
123
    }
124
125
    /**
126
     * @test
127
     */
128
    public function canCallInvokableObject()
129
    {
130
        $container = new Venta\Container\Container;
131
        $invokable = new TestClassFactory(new stdClass());
132
        $result = $container->call($invokable);
133
134
        $this->assertInstanceOf(TestClassContract::class, $result);
135
    }
136
137
    /**
138
     * @test
139
     */
140
    public function canCallObjectMethodFromArrayCallable()
141
    {
142
        $container = new Venta\Container\Container;
143
        $result = $container->call([new TestClassFactory(new stdClass()), 'createAndSetValue']);
144
145
        $this->assertInstanceOf(TestClassContract::class, $result);
146
        $this->assertInstanceOf(stdClass::class, $result->getValue());
147
    }
148
149
    /**
150
     * @test
151
     */
152
    public function canCheckEntryIsResolvable()
153
    {
154
        $container = new Venta\Container\Container;
155
        $container->bindClass(TestClassContract::class, TestClass::class);
156
157
        $this->assertTrue($container->has(TestClassContract::class));
158
        $this->assertTrue($container->has(stdClass::class));
159
        $this->assertFalse($container->has('UnknownInterface'));
160
    }
161
162
    /**
163
     * @test
164
     */
165
    public function canDecorateConcreteInstance()
166
    {
167
        $container = new Venta\Container\Container;
168
        $container->bindInstance(TestClassContract::class, new TestClass(new stdClass));
169
        $container->decorate(TestClassContract::class, function ($previous) {
170
            return new class($previous) implements TestClassContract
171
            {
172
            };
173
        });
174
175
        $concrete = $container->get(TestClassContract::class);
176
177
        $this->assertInstanceOf(TestClassContract::class, $concrete);
178
        $this->assertNotInstanceOf(TestClass::class, $concrete);
179
    }
180
181
    /**
182
     * @test
183
     */
184
    public function canDecorateFactoryDefinedBinding()
185
    {
186
        $container = new Venta\Container\Container;
187
        $container->bindFactory(TestClassContract::class, function () {
188
            return new TestClass(new stdClass);
189
        });
190
        $container->decorate(TestClassContract::class, function ($previous) {
191
            return new class($previous) implements TestClassContract
192
            {
193
            };
194
        });
195
196
        $concrete = $container->get(TestClassContract::class);
197
198
        $this->assertInstanceOf(TestClassContract::class, $concrete);
199
        $this->assertNotInstanceOf(TestClass::class, $concrete);
200
    }
201
202
    /**
203
     * @test
204
     */
205
    public function canDecoratePreviousImplementation()
206
    {
207
        $container = new Venta\Container\Container;
208
        $container->bindClass(TestClassContract::class, TestClass::class);
209
        $container->decorate(TestClassContract::class, function ($previous) {
210
            return new class($previous) implements TestClassContract
211
            {
212
            };
213
        });
214
215
        $concrete = $container->get(TestClassContract::class);
216
217
        $this->assertInstanceOf(TestClassContract::class, $concrete);
218
        $this->assertNotInstanceOf(TestClass::class, $concrete);
219
    }
220
221
    /**
222
     * @test
223
     */
224
    public function canResolveClassWithConstructorParameters()
225
    {
226
        $container = new Venta\Container\Container;
227
228
        $this->assertInstanceOf(
229
            'SimpleConstructorParametersClass',
230
            $container->get('SimpleConstructorParametersClass')
231
        );
232
        $this->assertInstanceOf(stdClass::class, $container->get('SimpleConstructorParametersClass')->getItem());
233
    }
234
235
    /**
236
     * @test
237
     */
238
    public function canResolveContractToContractBinding()
239
    {
240
        $container = new Venta\Container\Container;
241
        $container->bindClass(TestClassContract::class, TestClass::class);
242
        $container->bindClass(Contract::class, TestClassContract::class);
243
244
        $this->assertInstanceOf(TestClass::class, $container->get(Contract::class));
245
    }
246
247
    /**
248
     * @test
249
     */
250
    public function canResolveFromAbstractClassNameStaticMethod()
251
    {
252
        $container = new Venta\Container\Container;
253
        $container->bindFactory(TestClassContract::class, 'StaticTestFactory::create');
254
255
        $this->assertInstanceOf(TestClassContract::class, $container->get(TestClassContract::class));
256
    }
257
258
    /**
259
     * @test
260
     */
261
    public function canResolveFromClassName()
262
    {
263
        $container = new Venta\Container\Container;
264
265
        $this->assertInstanceOf(stdClass::class, $container->get('\stdClass'));
266
        $this->assertInstanceOf(stdClass::class, $container->get('stdClass'));
267
    }
268
269
    /**
270
     * @test
271
     */
272
    public function canResolveFromClassNameMethod()
273
    {
274
        $container = new Venta\Container\Container;
275
        $container->bindFactory(TestClassContract::class, 'TestClassFactory::create');
276
277
        $this->assertInstanceOf(TestClassContract::class, $container->get(TestClassContract::class));
278
    }
279
280
    /**
281
     * @test
282
     */
283
    public function canResolveFromClassNameMethodArray()
284
    {
285
        $container = new Venta\Container\Container;
286
        $container->bindFactory(TestClassContract::class, [TestClassFactory::class, 'create']);
287
288
        $this->assertInstanceOf(TestClassContract::class, $container->get(TestClassContract::class));
289
    }
290
291
    /**
292
     * @test
293
     */
294
    public function canResolveFromClassNameStaticMethod()
295
    {
296
        $container = new Venta\Container\Container;
297
        $container->bindFactory(TestClassContract::class, 'TestClassFactory::staticCreate');
298
299
        $this->assertInstanceOf(TestClassContract::class, $container->get(TestClassContract::class));
300
    }
301
302
    /**
303
     * @test
304
     */
305
    public function canResolveFromClosure()
306
    {
307
        $container = new Venta\Container\Container;
308
        $container->bindFactory(stdClass::class, function () {
309
            return new stdClass;
310
        });
311
312
        $this->assertInstanceOf(stdClass::class, $container->get(stdClass::class));
313
    }
314
315
    /**
316
     * @test
317
     */
318
    public function canResolveFromClosureWithArguments()
319
    {
320
        $container = new Venta\Container\Container;
321
        $container->bindFactory(TestClassContract::class, function (TestClass $class) {
322
            return $class;
323
        });
324
325
        $this->assertInstanceOf(TestClassContract::class, $container->get(TestClassContract::class));
326
    }
327
328
    /**
329
     * @test
330
     */
331
    public function canResolveFromFunctionName()
332
    {
333
        $container = new Venta\Container\Container;
334
        $container->bindFactory(TestClassContract::class, 'createTestClass');
335
336
        $this->assertInstanceOf(TestClassContract::class, $container->get(TestClassContract::class));
337
    }
338
339
    /**
340
     * @test
341
     */
342
    public function canResolveFromInvokableClassName()
343
    {
344
        $container = new Venta\Container\Container;
345
        $container->bindClass(TestClassFactory::class, TestClassFactory::class);
346
347
        $this->assertInstanceOf(TestClassFactory::class, $container->get(TestClassFactory::class));
348
    }
349
350
    /**
351
     * @test
352
     */
353
    public function canResolveFromInvokableObject()
354
    {
355
        $factory = Mockery::mock(TestClassFactory::class)
356
                          ->shouldReceive('__invoke')
357
                          ->withNoArgs()
358
                          ->andReturn(Mockery::mock(TestClassContract::class))
359
                          ->once()
360
                          ->getMock();
361
362
        $container = new Venta\Container\Container;
363
        $container->bindFactory(TestClassContract::class, $factory);
364
        $this->assertInstanceOf(TestClassContract::class, $container->get(TestClassContract::class));
365
    }
366
367
    /**
368
     * @test
369
     */
370
    public function canResolveFromObjectMethodArray()
371
    {
372
        $factory = Mockery::mock(TestClassFactory::class)
373
                          ->shouldReceive('create')
374
                          ->withNoArgs()
375
                          ->andReturn(Mockery::mock(TestClassContract::class))
376
                          ->once()
377
                          ->getMock();
378
379
        $container = new Venta\Container\Container;
380
        $container->bindFactory(TestClassContract::class, [$factory, 'create']);
381
        $this->assertInstanceOf(TestClassContract::class, $container->get(TestClassContract::class));
382
    }
383
384
    /**
385
     * @test
386
     */
387
    public function canResolveInstanceAsShared()
388
    {
389
        $container = new Venta\Container\Container;
390
        $container->bindInstance(\Venta\Contracts\Container\Container::class, $this);
391
392
        $this->assertSame($this, $container->get(\Venta\Contracts\Container\Container::class));
393
        $this->assertSame(
394
            $container->get(\Venta\Contracts\Container\Container::class),
395
            $container->get(\Venta\Contracts\Container\Container::class)
396
        );
397
    }
398
399
    /**
400
     * @test
401
     */
402
    public function canResolveSharedFromClosure()
403
    {
404
        $container = new Venta\Container\Container;
405
        $container->bindFactory(stdClass::class, function () {
406
            return new stdClass;
407
        }, true);
408
409
        $this->assertSame($container->get(stdClass::class), $container->get(stdClass::class));
410
    }
411
412
    /**
413
     * @test
414
     * @expectedException \Venta\Container\Exception\CircularReferenceException
415
     */
416
    public function checksDirectCircularDependency()
417
    {
418
        $container = new Venta\Container\Container;
419
        $container->get(D::class);
420
    }
421
422
    /**
423
     * @test
424
     */
425
    public function checksIfServiceMethodIsCallable()
426
    {
427
        $container = new Venta\Container\Container;
428
429
        $this->assertTrue($container->isCallable('TestClassFactory::create'));
430
        $this->assertFalse($container->isCallable('TestClassFactoryContract::create'));
431
    }
432
433
    /**
434
     * @test
435
     * @expectedException \Venta\Container\Exception\CircularReferenceException
436
     */
437
    public function checksIndirectCircularDependency()
438
    {
439
        $container = new Venta\Container\Container;
440
        $container->get(A::class);
441
    }
442
443
    /**
444
     * @test
445
     * @expectedException \Venta\Container\Exception\CircularReferenceException
446
     */
447
    public function checksInflectionCircularDependency()
448
    {
449
        $container = new Venta\Container\Container;
450
        $container->addInflection(E::class, 'setDependency');
451
        $container->get(E::class);
452
    }
453
454
    /**
455
     * @test
456
     */
457
    public function savesReflectionResolveResult()
458
    {
459
        $closure = function (stdClass $dep) {
460
            return new TestClass($dep);
461
        };
462
        $reflection = new ReflectionFunction($closure);
463
        $resolver = Mockery::mock(\Venta\Contracts\Container\ArgumentResolver::class);
464
        $resolver->shouldReceive('reflectCallable')
465
                 ->with($closure)
466
                 ->andReturn($reflection)
467
                 ->once();
468
        $resolver->shouldReceive('resolveArguments')
469
                 ->with($reflection)
470
                 ->andReturn(function () {
471
                     return [new stdClass()];
472
                 })
473
                 ->once();
474
475
        $inflector = Mockery::mock(ObjectInflector::class);
476
        $inflector->shouldReceive('applyInflections');
477
478
        /** @var Container $container */
479
        $container = new class($resolver, $inflector) extends Container
480
        {
481
            /**
482
             * @inheritDoc
483
             */
484
            public function __construct(ArgumentResolver $resolver = null, ObjectInflector $inflector)
485
            {
486
                $this->setArgumentResolver($resolver);
487
                $this->setObjectInflector($inflector);
488
            }
489
490
        };
491
        $container->bindFactory(TestClassContract::class, $closure);
492
493
        $container->get(TestClassContract::class);
494
        $container->get(TestClassContract::class);
495
    }
496
497
    /**
498
     * @test
499
     * @expectedException \Venta\Container\Exception\UnresolvableDependencyException
500
     */
501
    public function throwsContainerExceptionIfCantResolve()
502
    {
503
        $container = new Venta\Container\Container;
504
        $container->bindFactory(TestClassContract::class, function ($someUnresolvableDependency) {
505
        });
506
        $container->get(TestClassContract::class);
507
    }
508
509
    /**
510
     * @test
511
     * @expectedException InvalidArgumentException
512
     */
513
    public function throwsExceptionIfCallingNotCallable()
514
    {
515
        $container = new Venta\Container\Container;
516
        $container->call(42);
517
    }
518
519
    /**
520
     * @test
521
     * @expectedException InvalidArgumentException
522
     */
523
    public function throwsExceptionIfEntryClassNameIsInvalid()
524
    {
525
        $container = new Venta\Container\Container;
526
        $container->bindClass(TestClassContract::class, 'Some unknown class');
527
    }
528
529
    /**
530
     * @test
531
     * @expectedException InvalidArgumentException
532
     */
533
    public function throwsExceptionIfIdIsInvalid()
534
    {
535
        $container = new Venta\Container\Container;
536
        $container->bindClass('Some unknown interface', TestClass::class);
537
    }
538
539
    /**
540
     * @test
541
     * @expectedException InvalidArgumentException
542
     */
543
    public function throwsExceptionIfInflectionMethodDoesNotExist()
544
    {
545
        $container = new Venta\Container\Container;
546
        $container->addInflection(TestClass::class, 'unknownMethod');
547
    }
548
549
    /**
550
     * @test
551
     * @expectedException InvalidArgumentException
552
     */
553
    public function throwsExceptionOnInvalidCallableCall()
554
    {
555
        $container = new Venta\Container\Container;
556
        $container->call('SomeInvalidCallableToCall');
557
    }
558
559
    /**
560
     * @test
561
     * @expectedException Venta\Container\Exception\UninstantiableServiceException
562
     */
563
    public function throwsExceptionWhenBoundToUninstantiableClass()
564
    {
565
        $container = new Venta\Container\Container;
566
        $container->bindClass(TestClassFactoryContract::class, StaticTestFactory::class);
567
568
        $container->get(TestClassFactoryContract::class);
569
    }
570
571
    /**
572
     * @test
573
     * @expectedException Venta\Container\Exception\NotFoundException
574
     */
575
    public function throwsExceptionWhenCallsUnresolvableServiceMethod()
576
    {
577
        $container = new Venta\Container\Container;
578
        $container->call('TestClassFactoryContract::create');
579
    }
580
581
    /**
582
     * @test
583
     * @expectedException \Interop\Container\Exception\NotFoundException
584
     */
585
    public function throwsNotFoundExceptionIfNotResolvable()
586
    {
587
        $container = new Venta\Container\Container;
588
        $container->get(TestClassContract::class);
589
    }
590
}
591