Passed
Pull Request — master (#17)
by Aleksei
11:42
created

InjectorTest   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 645
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 248
dl 0
loc 645
rs 8.96
c 0
b 0
f 0
wmc 43

88 Methods

Rating   Name   Duplication   Size   Complexity  
testInvokeReferencedArgumentNamedVariadic() 0 21 ?
testUnnamedScalarParam() 0 9 ?
testAloneScalarVariadicArgumentAnsNamedParam() 0 9 ?
testMakeInterface() 0 6 ?
A hp$0 ➔ testInvokeWithObjectType() 0 8 1
testMissingRequiredTypedParameter() 0 12 ?
A testInvokeClosure() 0 9 1
A hp$0 ➔ testMissingRequiredNotTypedParameter() 0 12 1
A hp$0 ➔ testTwoEqualCustomArgumentsWithOneCustomNamedParameter() 0 12 1
A hp$0 ➔ testWithNullableArgumentAndEmptyContainer() 0 9 1
testMissingRequiredNotTypedParameter() 0 12 ?
testExtendedArgumentsWithOneCustomNamedParameter2() 0 14 ?
A hp$0 ➔ testWithNullableOptionalArgumentThatNull() 0 9 1
A hp$0 ➔ testAppendingUnusedParams() 0 20 1
A hp$0 ➔ testVariadicMixedArgumentWithMixedParams() 0 12 1
A hp$0 ➔ testTwoEqualCustomArgumentsWithOneCustom() 0 12 1
testMakeInvalidClass() 0 10 ?
A hp$0 ➔ testMissingRequiredTypedParameter() 0 12 1
testMakeWithVariadicFromContainer() 0 7 ?
testWithNullableScalarArgument() 0 9 ?
testMakeWithInvalidCustomParam() 0 7 ?
testMakeWithoutArguments() 0 7 ?
testAppendingUnusedParams() 0 20 ?
A hp$0 ➔ testInvokeReferencedArgumentNamedVariadic() 0 21 1
testWithNullableOptionalArgumentThatNull() 0 9 ?
A hp$0 ➔ testNotFoundException() 0 12 1
A hp$0 ➔ testMakeWithoutArguments() 0 7 1
testInvokeReferencedArgument() 0 10 ?
A hp$0 ➔ testUnnamedScalarParam() 0 9 1
A hp$0 ➔ testMakeInternalClass() 0 5 1
testInvokeWithoutArguments() 0 9 ?
testMakeWithPrivateConstructor() 0 8 ?
testWithNullableArgument() 0 9 ?
A hp$0 ➔ testInvokeable() 0 5 1
testVariadicArgumentUnnamedParams() 0 12 ?
A hp$0 ➔ testMakeWithCustomParam() 0 11 1
A hp$0 ➔ testMakeWithVariadicFromArguments() 0 8 1
testMakeWithVariadicFromArguments() 0 8 ?
A testInvokeCallableArray() 0 9 1
A hp$0 ➔ testWithNullableArgument() 0 9 1
testMakeInternalClass() 0 5 ?
testWithNullableArgumentAndEmptyContainer() 0 9 ?
testMakeWithoutConstructor() 0 7 ?
A hp$0 ➔ testMakeWithInvalidCustomParam() 0 7 1
testNotFoundException() 0 12 ?
A hp$0 ➔ testInvokeWithoutArguments() 0 9 1
A hp$0 ➔ testArrayArgumentWithUnnamedType() 0 9 1
A hp$0 ➔ testWithNullableOptionalArgument() 0 9 1
testInvokeWithObjectType() 0 8 ?
testIterableArgumentWithUnnamedType() 0 9 ?
testInvokeAnonymousClass() 0 14 ?
A hp$0 ➔ testVariadicArgumentUnnamedParams() 0 12 1
testArrayArgumentWithUnnamedType() 0 9 ?
A hp$0 ➔ testIterableArgumentWithUnnamedType() 0 9 1
A hp$0 ➔ testNullableVariadicArgument() 0 9 1
testNullableVariadicArgument() 0 9 ?
A hp$0 ➔ testExtendedArgumentsWithOneCustomNamedParameter2() 0 14 1
A hp$0 ➔ testCallableArgumentWithUnnamedType() 0 9 1
A hp$0 ➔ testCustomDependency() 0 13 1
testCustomDependency() 0 13 ?
A hp$0 ➔ testMakeInterface() 0 6 1
testTwoEqualCustomArgumentsWithOneCustomNamedParameter() 0 12 ?
A hp$0 ➔ testInvokeReferencedArguments() 0 40 1
testCallableArgumentWithUnnamedType() 0 9 ?
A hp$0 ➔ testInvokeWithRequiredObjectTypeWithoutInstance() 0 8 1
A hp$0 ➔ testVariadicStringArgumentWithUnnamedStringsParams() 0 9 1
A hp$0 ➔ testWrongNamedParam() 0 9 1
A hp$0 ➔ testMakeAbstractClass() 0 6 1
testWithNullableOptionalArgument() 0 9 ?
testVariadicMixedArgumentWithMixedParams() 0 12 ?
testInvokeable() 0 5 ?
testWrongNamedParam() 0 9 ?
A hp$0 ➔ testMakeInvalidClass() 0 10 1
A hp$0 ➔ testInvokeAnonymousClass() 0 14 1
A hp$0 ➔ testMakeWithPrivateConstructor() 0 8 1
A hp$0 ➔ testMakeWithoutConstructor() 0 7 1
A hp$0 ➔ testWithNullableScalarArgument() 0 9 1
A hp$0 ➔ testMakeWithVariadicFromContainer() 0 7 1
A hp$0 ➔ testAloneScalarVariadicArgumentAnsNamedParam() 0 9 1
testVariadicStringArgumentWithUnnamedStringsParams() 0 9 ?
testTwoEqualCustomArgumentsWithOneCustom() 0 12 ?
A hp$0 ➔ testInvokeReferencedArgument() 0 10 1
A hp$0 ➔ setEngine() 0 3 1
testMakeAbstractClass() 0 6 ?
testInvokeWithRequiredObjectTypeWithoutInstance() 0 8 ?
testInvokeReferencedArguments() 0 40 ?
testMakeWithCustomParam() 0 11 ?
A testInvokeStatic() 0 7 1

How to fix   Complexity   

Complex Class

Complex classes like InjectorTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use InjectorTest, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Injector\Tests\Common;
6
7
use DateTime;
8
use DateTimeImmutable;
9
use DateTimeInterface;
10
use Psr\Container\NotFoundExceptionInterface;
11
use stdClass;
12
use Yiisoft\Injector\Injector;
13
use Yiisoft\Injector\InvalidArgumentException;
14
use Yiisoft\Injector\MissingRequiredArgumentException;
15
use Yiisoft\Injector\Tests\Common\Support\ColorInterface;
16
use Yiisoft\Injector\Tests\Common\Support\EngineInterface;
17
use Yiisoft\Injector\Tests\Common\Support\EngineMarkTwo;
18
use Yiisoft\Injector\Tests\Common\Support\EngineZIL130;
19
use Yiisoft\Injector\Tests\Common\Support\EngineVAZ2101;
20
use Yiisoft\Injector\Tests\Common\Support\Invokeable;
21
use Yiisoft\Injector\Tests\Common\Support\LightEngine;
22
use Yiisoft\Injector\Tests\Common\Support\MakeEmptyConstructor;
23
use Yiisoft\Injector\Tests\Common\Support\MakeEngineCollector;
24
use Yiisoft\Injector\Tests\Common\Support\MakeEngineMatherWithParam;
25
use Yiisoft\Injector\Tests\Common\Support\MakeNoConstructor;
26
use Yiisoft\Injector\Tests\Common\Support\MakePrivateConstructor;
27
28
class InjectorTest extends BaseInjectorTest
29
{
30
    /**
31
     * Injector should be able to invoke closure.
32
     */
33
    public function testInvokeClosure(): void
34
    {
35
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
36
37
        $getEngineName = fn (EngineInterface $engine) => $engine->getName();
38
39
        $engineName = (new Injector($container))->invoke($getEngineName);
40
41
        $this->assertSame('Mark Two', $engineName);
42
    }
43
44
    /**
45
     * Injector should be able to invoke array callable.
46
     */
47
    public function testInvokeCallableArray(): void
48
    {
49
        $container = $this->getContainer();
50
51
        $object = new EngineVAZ2101();
52
53
        $engine = (new Injector($container))->invoke([$object, 'rust'], ['index' => 5.0]);
54
55
        $this->assertInstanceOf(EngineVAZ2101::class, $engine);
56
    }
57
58
    /**
59
     * Injector should be able to invoke static method.
60
     */
61
    public function testInvokeStatic(): void
62
    {
63
        $container = $this->getContainer();
64
65
        $result = (new Injector($container))->invoke([EngineVAZ2101::class, 'isWroomWroom']);
66
67
        $this->assertIsBool($result);
68
    }
69
70
    /**
71
     * Injector should be able to invoke static method.
72
     */
73
    public function testInvokeAnonymousClass(): void
74
    {
75
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
76
        $class = new class() {
77
            public EngineInterface $engine;
78
            public function setEngine(EngineInterface $engine): void
79
            {
80
                $this->engine = $engine;
81
            }
82
        };
83
84
        (new Injector($container))->invoke([$class, 'setEngine']);
85
86
        $this->assertInstanceOf(EngineInterface::class, $class->engine);
87
    }
88
89
    /**
90
     * Injector should be able to invoke method without arguments.
91
     */
92
    public function testInvokeWithoutArguments(): void
93
    {
94
        $container = $this->getContainer();
95
96
        $true = fn () => true;
97
98
        $result = (new Injector($container))->invoke($true);
99
100
        $this->assertTrue($result);
101
    }
102
103
    /**
104
     * Nullable arguments should be searched in container.
105
     */
106
    public function testWithNullableArgument(): void
107
    {
108
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
109
110
        $nullable = fn (?EngineInterface $engine) => $engine;
111
112
        $result = (new Injector($container))->invoke($nullable);
113
114
        $this->assertNotNull($result);
115
    }
116
117
    /**
118
     * Nullable arguments not found in container should be passed as `null`.
119
     */
120
    public function testWithNullableArgumentAndEmptyContainer(): void
121
    {
122
        $container = $this->getContainer();
123
124
        $nullable = fn (?EngineInterface $engine) => $engine;
125
126
        $result = (new Injector($container))->invoke($nullable);
127
128
        $this->assertNull($result);
129
    }
130
131
    /**
132
     * Nullable scalars should be set with `null` if not specified by name explicitly.
133
     */
134
    public function testWithNullableScalarArgument(): void
135
    {
136
        $container = $this->getContainer();
137
138
        $nullableInt = fn (?int $number) => $number;
139
140
        $result = (new Injector($container))->invoke($nullableInt);
141
142
        $this->assertNull($result);
143
    }
144
145
    /**
146
     * Optional scalar arguments should be set with default value if not specified by name explicitly.
147
     */
148
    public function testWithNullableOptionalArgument(): void
149
    {
150
        $container = $this->getContainer();
151
152
        $nullableInt = fn (?int $number = 6) => $number;
153
154
        $result = (new Injector($container))->invoke($nullableInt);
155
156
        $this->assertSame(6, $result);
157
    }
158
159
    /**
160
     * Optional arguments with `null` by default should be set with `null` if other value not specified in parameters
161
     * or container.
162
     */
163
    public function testWithNullableOptionalArgumentThatNull(): void
164
    {
165
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
166
167
        $callable = fn (EngineInterface $engine = null) => $engine;
168
169
        $result = (new Injector($container))->invoke($callable);
170
171
        $this->assertNotNull($result);
172
    }
173
174
    /**
175
     * An object for a typed argument can be specified in parameters without named key and without following the order.
176
     */
177
    public function testCustomDependency(): void
178
    {
179
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
180
        $needleEngine = new EngineZIL130();
181
182
        $getEngineName = fn (EngineInterface $engine) => $engine->getName();
183
184
        $engineName = (new Injector($container))->invoke(
185
            $getEngineName,
186
            [new stdClass(), $needleEngine, new DateTimeImmutable()]
187
        );
188
189
        $this->assertSame(EngineZIL130::NAME, $engineName);
190
    }
191
192
    /**
193
     * In this case, first argument will be set from parameters, and second argument from container.
194
     */
195
    public function testTwoEqualCustomArgumentsWithOneCustom(): void
196
    {
197
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
198
199
        $compareEngines = static function (EngineInterface $engine1, EngineInterface $engine2) {
200
            return $engine1->getPower() <=> $engine2->getPower();
201
        };
202
        $zilEngine = new EngineZIL130();
203
204
        $result = (new Injector($container))->invoke($compareEngines, [$zilEngine]);
205
206
        $this->assertSame(-1, $result);
207
    }
208
209
    /**
210
     * In this case, second argument will be set from parameters by name, and first argument from container.
211
     */
212
    public function testTwoEqualCustomArgumentsWithOneCustomNamedParameter(): void
213
    {
214
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
215
216
        $compareEngines = static function (EngineInterface $engine1, EngineInterface $engine2) {
217
            return $engine1->getPower() <=> $engine2->getPower();
218
        };
219
        $zilEngine = new EngineZIL130();
220
221
        $result = (new Injector($container))->invoke($compareEngines, ['engine2' => $zilEngine]);
222
223
        $this->assertSame(1, $result);
224
    }
225
226
    /**
227
     * Values for arguments are not matched by the greater similarity of parameter types and arguments, but simply pass
228
     * in order as is.
229
     */
230
    public function testExtendedArgumentsWithOneCustomNamedParameter2(): void
231
    {
232
        $container = $this->getContainer([LightEngine::class => new EngineVAZ2101()]);
233
234
        $concatEngineNames = static function (EngineInterface $engine1, LightEngine $engine2) {
235
            return $engine1->getName() . $engine2->getName();
236
        };
237
238
        $result = (new Injector($container))->invoke($concatEngineNames, [
239
            new EngineMarkTwo(), // LightEngine, EngineInterface
240
            new EngineZIL130(), // EngineInterface
241
        ]);
242
243
        $this->assertSame(EngineMarkTwo::NAME . EngineVAZ2101::NAME, $result);
244
    }
245
246
    public function testMissingRequiredTypedParameter(): void
247
    {
248
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
249
250
        $getEngineName = static function (EngineInterface $engine, string $two) {
251
            return $engine->getName() . $two;
252
        };
253
254
        $injector = new Injector($container);
255
256
        $this->expectException(MissingRequiredArgumentException::class);
257
        $injector->invoke($getEngineName);
258
    }
259
260
    public function testMissingRequiredNotTypedParameter(): void
261
    {
262
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
263
264
        $getEngineName = static function (EngineInterface $engine, $two) {
265
            return $engine->getName() . $two;
266
        };
267
        $injector = new Injector($container);
268
269
        $this->expectException(MissingRequiredArgumentException::class);
270
271
        $injector->invoke($getEngineName);
272
    }
273
274
    public function testNotFoundException(): void
275
    {
276
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
277
278
        $getEngineName = static function (EngineInterface $engine, ColorInterface $color) {
279
            return $engine->getName() . $color->getColor();
280
        };
281
282
        $injector = new Injector($container);
283
284
        $this->expectException(NotFoundExceptionInterface::class);
285
        $injector->invoke($getEngineName);
286
    }
287
288
    /**
289
     * A values collection for a variadic argument can be passed as an array in a named parameter.
290
     */
291
    public function testAloneScalarVariadicArgumentAnsNamedParam(): void
292
    {
293
        $container = $this->getContainer();
294
295
        $callable = fn (int ...$var) => array_sum($var);
296
297
        $result = (new Injector($container))->invoke($callable, ['var' => [1, 2, 3], new stdClass()]);
298
299
        $this->assertSame(6, $result);
300
    }
301
302
    /**
303
     * If type of a variadic argument is a class and named parameter with values collection is not set then injector
304
     * will search for objects by class name among all unnamed parameters.
305
     */
306
    public function testVariadicArgumentUnnamedParams(): void
307
    {
308
        $container = $this->getContainer([DateTimeInterface::class => new DateTimeImmutable()]);
309
310
        $callable = fn (DateTimeInterface $dateTime, EngineInterface ...$engines) => count($engines);
311
312
        $result = (new Injector($container))->invoke(
313
            $callable,
314
            [new EngineZIL130(), new EngineVAZ2101(), new stdClass(), new EngineMarkTwo(), new stdClass()]
315
        );
316
317
        $this->assertSame(3, $result);
318
    }
319
320
    /**
321
     * If calling method have an untyped variadic argument then all remaining unnamed parameters will be passed.
322
     */
323
    public function testVariadicMixedArgumentWithMixedParams(): void
324
    {
325
        $container = $this->getContainer([DateTimeInterface::class => new DateTimeImmutable()]);
326
327
        $callable = fn (...$engines) => $engines;
328
329
        $result = (new Injector($container))->invoke(
330
            $callable,
331
            [new EngineZIL130(), new EngineVAZ2101(), new EngineMarkTwo(), new stdClass()]
332
        );
333
334
        $this->assertCount(4, $result);
335
    }
336
337
    /**
338
     * Any unnamed parameter can only be an object. Scalar, array, null and other values can only be named parameters.
339
     */
340
    public function testVariadicStringArgumentWithUnnamedStringsParams(): void
341
    {
342
        $container = $this->getContainer([DateTimeInterface::class => new DateTimeImmutable()]);
343
344
        $callable = fn (string ...$engines) => $engines;
345
346
        $this->expectException(\Exception::class);
347
348
        (new Injector($container))->invoke($callable, ['str 1', 'str 2', 'str 3']);
349
    }
350
351
    /**
352
     * In the absence of other values to a nullable variadic argument `null` is not passed by default.
353
     */
354
    public function testNullableVariadicArgument(): void
355
    {
356
        $container = $this->getContainer();
357
358
        $callable = fn (?EngineInterface ...$engines) => $engines;
359
360
        $result = (new Injector($container))->invoke($callable, []);
361
362
        $this->assertSame([], $result);
363
    }
364
365
    /**
366
     * Parameters that were passed but were not used are appended to the call so they could be obtained
367
     * with func_get_args().
368
     */
369
    public function testAppendingUnusedParams(): void
370
    {
371
        $container = $this->getContainer();
372
373
        $callable = static function (
374
            /** @scrutinizer ignore-unused */
375
            ?EngineInterface $engine,
376
            /** @scrutinizer ignore-unused */
377
            $id = 'test'
378
        ) {
379
            return func_num_args();
380
        };
381
382
        $result = (new Injector($container))->invoke($callable, [
383
            new DateTimeImmutable(),
384
            new DateTimeImmutable(),
385
            new EngineMarkTwo()
386
        ]);
387
388
        $this->assertSame(4, $result);
389
    }
390
391
    /**
392
     * Object type may be passed as unnamed parameter
393
     */
394
    public function testInvokeWithObjectType(): void
395
    {
396
        $container = $this->getContainer();
397
        $callable = fn (object $object) => get_class($object);
398
399
        $result = (new Injector($container))->invoke($callable, [new DateTimeImmutable()]);
400
401
        $this->assertSame(DateTimeImmutable::class, $result);
402
    }
403
404
    /**
405
     * Required `object` type should not be requested from the container
406
     */
407
    public function testInvokeWithRequiredObjectTypeWithoutInstance(): void
408
    {
409
        $container = $this->getContainer();
410
        $callable = fn (object $object) => get_class($object);
411
412
        $this->expectException(MissingRequiredArgumentException::class);
413
414
        (new Injector($container))->invoke($callable);
415
    }
416
417
    /**
418
     * Arguments passed by reference
419
     */
420
    public function testInvokeReferencedArguments(): void
421
    {
422
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
423
        $foo = 1;
424
        $bar = new stdClass();
425
        $baz = null;
426
        $callable = static function (
427
            int &$foo,
428
            object &$bar,
429
            &$baz,
430
            ?ColorInterface &$nullable,
431
            EngineInterface &$object, // from container
432
            DateTimeInterface &...$dates // collect all unnamed DateTimeInterface objects
433
        ) {
434
            $return = func_get_args();
435
            $bar = new DateTimeImmutable();
436
            $baz = false;
437
            $foo = count($dates);
438
            return $return;
439
        };
440
        $result = (new Injector($container))
441
            ->invoke($callable, [
442
                new DateTimeImmutable(),
443
                new DateTime(),
444
                new DateTime(),
445
                'foo' => &$foo,
446
                'bar' => $bar,
447
                'baz' => &$baz,
448
            ]);
449
450
        // passed
451
        $this->assertSame(1, $result[0]);
452
        $this->assertInstanceOf(stdClass::class, $result[1]);
453
        $this->assertNull($result[2]);
454
        $this->assertNull($result[3]);
455
        $this->assertInstanceOf(EngineMarkTwo::class, $result[4]);
456
        // transformed
457
        $this->assertSame(3, $foo); // count of DateTimeInterface objects
458
        $this->assertInstanceOf(stdClass::class, $bar);
459
        $this->assertFalse($baz);
460
    }
461
462
    public function testInvokeReferencedArgumentNamedVariadic(): void
463
    {
464
        $container = $this->getContainer();
465
466
        $callable = static function (DateTimeInterface &...$dates) {
467
            $dates[0] = false;
468
            $dates[1] = false;
469
            return count($dates);
470
        };
471
        $foo = new DateTimeImmutable();
472
        $bar = new DateTimeImmutable();
473
        $result = (new Injector($container))
474
            ->invoke($callable, [
475
                $foo,
476
                &$bar,
477
                new DateTime(),
478
            ]);
479
480
        $this->assertSame(3, $result);
481
        $this->assertInstanceOf(DateTimeImmutable::class, $foo);
482
        $this->assertFalse($bar);
483
    }
484
485
    /**
486
     * If argument passed by reference but it is not supported by function
487
     */
488
    public function testInvokeReferencedArgument(): void
489
    {
490
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
491
        $foo = 1;
492
        $callable = fn (int $foo) => ++$foo;
493
        $result = (new Injector($container))->invoke($callable, ['foo' => &$foo]);
494
495
        // $foo has been not changed
496
        $this->assertSame(1, $foo);
497
        $this->assertSame(2, $result);
498
    }
499
500
    public function testWrongNamedParam(): void
501
    {
502
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
503
504
        $callable = fn (EngineInterface $engine) => $engine;
505
506
        $this->expectException(\Throwable::class);
507
508
        (new Injector($container))->invoke($callable, ['engine' => new DateTimeImmutable()]);
509
    }
510
511
    public function testArrayArgumentWithUnnamedType(): void
512
    {
513
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
514
515
        $callable = fn (array $arg) => $arg;
516
517
        $this->expectException(InvalidArgumentException::class);
518
519
        (new Injector($container))->invoke($callable, [['test']]);
520
    }
521
522
    public function testCallableArgumentWithUnnamedType(): void
523
    {
524
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
525
526
        $callable = fn (callable $arg) => $arg();
527
528
        $this->expectException(MissingRequiredArgumentException::class);
529
530
        (new Injector($container))->invoke($callable, [fn () => true]);
531
    }
532
533
    public function testIterableArgumentWithUnnamedType(): void
534
    {
535
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
536
537
        $callable = fn (iterable $arg) => $arg;
538
539
        $this->expectException(MissingRequiredArgumentException::class);
540
541
        (new Injector($container))->invoke($callable, [new \SplStack()]);
542
    }
543
544
    public function testUnnamedScalarParam(): void
545
    {
546
        $container = $this->getContainer();
547
548
        $getEngineName = fn () => 42;
549
550
        $this->expectException(InvalidArgumentException::class);
551
552
        (new Injector($container))->invoke($getEngineName, ['test']);
553
    }
554
555
    public function testInvokeable(): void
556
    {
557
        $container = $this->getContainer();
558
        $result = (new Injector($container))->invoke(new Invokeable());
559
        $this->assertSame(42, $result);
560
    }
561
562
    /**
563
     * Constructor method not defined
564
     */
565
    public function testMakeWithoutConstructor(): void
566
    {
567
        $container = $this->getContainer();
568
569
        $object = (new Injector($container))->make(MakeNoConstructor::class);
570
571
        $this->assertInstanceOf(MakeNoConstructor::class, $object);
572
    }
573
574
    /**
575
     * Constructor without arguments
576
     */
577
    public function testMakeWithoutArguments(): void
578
    {
579
        $container = $this->getContainer();
580
581
        $object = (new Injector($container))->make(MakeEmptyConstructor::class);
582
583
        $this->assertInstanceOf(MakeEmptyConstructor::class, $object);
584
    }
585
586
    /**
587
     * Private constructor unavailable from Injector context
588
     */
589
    public function testMakeWithPrivateConstructor(): void
590
    {
591
        $container = $this->getContainer();
592
593
        $this->expectException(\InvalidArgumentException::class);
594
        $this->expectExceptionMessageMatches('/not instantiable/');
595
596
        (new Injector($container))->make(MakePrivateConstructor::class);
597
    }
598
599
    public function testMakeInvalidClass(): void
600
    {
601
        $undefinedClass = '\\undefinedNameSpace\\UndefinedClassThatShouldNotBeDefined';
602
        $container = $this->getContainer();
603
604
        $this->assertFalse(class_exists($undefinedClass, true));
605
        $this->expectException(\ReflectionException::class);
606
        $this->expectExceptionMessageMatches('/does not exist/');
607
608
        (new Injector($container))->make($undefinedClass);
609
    }
610
611
    public function testMakeInternalClass(): void
612
    {
613
        $container = $this->getContainer();
614
        $object = (new Injector($container))->make(DateTimeImmutable::class);
615
        $this->assertInstanceOf(DateTimeImmutable::class, $object);
616
    }
617
618
    public function testMakeAbstractClass(): void
619
    {
620
        $container = $this->getContainer();
621
        $this->expectException(\InvalidArgumentException::class);
622
        $this->expectExceptionMessageMatches('/not instantiable/');
623
        (new Injector($container))->make(LightEngine::class);
624
    }
625
626
    public function testMakeInterface(): void
627
    {
628
        $container = $this->getContainer();
629
        $this->expectException(\InvalidArgumentException::class);
630
        $this->expectExceptionMessageMatches('/not instantiable/');
631
        (new Injector($container))->make(EngineInterface::class);
632
    }
633
634
    public function testMakeWithVariadicFromContainer(): void
635
    {
636
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
637
638
        $object = (new Injector($container))->make(MakeEngineCollector::class, []);
639
640
        $this->assertCount(0, $object->engines);
641
    }
642
643
    public function testMakeWithVariadicFromArguments(): void
644
    {
645
        $container = $this->getContainer();
646
647
        $values = [new EngineMarkTwo(), new EngineVAZ2101()];
648
        $object = (new Injector($container))->make(MakeEngineCollector::class, $values);
649
650
        $this->assertSame($values, $object->engines);
651
    }
652
653
    public function testMakeWithCustomParam(): void
654
    {
655
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
656
657
        $object = (new Injector($container))
658
            ->make(MakeEngineMatherWithParam::class, [new EngineVAZ2101(), 'parameter' => 'power']);
659
660
        $this->assertNotSame($object->engine1, $object->engine2);
661
        $this->assertInstanceOf(EngineVAZ2101::class, $object->engine1);
662
        $this->assertNotSame(EngineMarkTwo::class, $object->engine2);
663
        $this->assertSame($object->parameter, 'power');
664
    }
665
666
    public function testMakeWithInvalidCustomParam(): void
667
    {
668
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
669
670
        $this->expectException(\TypeError::class);
671
672
        (new Injector($container))->make(MakeEngineMatherWithParam::class, ['parameter' => 100500]);
673
    }
674
}
675