Passed
Pull Request — master (#3)
by Alexander
01:16
created

testTwoEqualCustomArgumentsWithOneCustom()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 1
Metric Value
cc 1
eloc 6
c 3
b 0
f 1
nc 1
nop 0
dl 0
loc 12
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Injector\Tests;
6
7
use PHPUnit\Framework\TestCase;
8
use Psr\Container\NotFoundExceptionInterface;
9
use Yiisoft\Di\Container;
10
use Yiisoft\Injector\Injector;
11
use Yiisoft\Injector\InvalidArgumentException;
12
use Yiisoft\Injector\MissingRequiredArgumentException;
13
use Yiisoft\Injector\Tests\Support\ColorInterface;
14
use Yiisoft\Injector\Tests\Support\EngineInterface;
15
use Yiisoft\Injector\Tests\Support\EngineMarkTwo;
16
use Yiisoft\Injector\Tests\Support\EngineZIL130;
17
use Yiisoft\Injector\Tests\Support\EngineVAZ2101;
18
use Yiisoft\Injector\Tests\Support\LightEngine;
19
20
class InjectorTest extends TestCase
21
{
22
    /**
23
     * Injector should be able to invoke closure.
24
     */
25
    public function testInvokeClosure(): void
26
    {
27
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
28
29
        $getEngineName = fn (EngineInterface $engine) => $engine->getName();
30
31
        $engineName = (new Injector($container))->invoke($getEngineName);
32
33
        $this->assertSame('Mark Two', $engineName);
34
    }
35
36
    /**
37
     * Injector should be able to invoke array callable.
38
     */
39
    public function testInvokeCallableArray(): void
40
    {
41
        $container = new Container([]);
42
43
        $object = new EngineVAZ2101();
44
45
        $engine = (new Injector($container))->invoke([$object, 'rust'], ['index' => 5.0]);
46
47
        $this->assertInstanceOf(EngineVAZ2101::class, $engine);
48
    }
49
50
    /**
51
     * Injector should be able to invoke static method.
52
     */
53
    public function testInvokeStatic(): void
54
    {
55
        $container = new Container([]);
56
57
        $result = (new Injector($container))->invoke([EngineVAZ2101::class, 'isWroomWroom']);
58
59
        $this->assertIsBool($result);
60
    }
61
62
    /**
63
     * Injector should be able to invoke method without arguments.
64
     */
65
    public function testInvokeWithoutArguments(): void
66
    {
67
        $container = new Container([]);
68
69
        $true = fn () => true;
70
71
        $result = (new Injector($container))->invoke($true);
72
73
        $this->assertTrue($result);
74
    }
75
76
    /**
77
     * Nullable arguments should be searched in container.
78
     */
79
    public function testWithNullableArgument(): void
80
    {
81
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
82
83
        $nullable = fn (?EngineInterface $engine) => $engine;
84
85
        $result = (new Injector($container))->invoke($nullable);
86
87
        $this->assertNotNull($result);
88
    }
89
90
    /**
91
     * Nullable arguments not found in container should be passed as `null`.
92
     */
93
    public function testWithNullableArgumentAndEmptyContainer(): void
94
    {
95
        $container = new Container([]);
96
97
        $nullable = fn (?EngineInterface $engine) => $engine;
98
99
        $result = (new Injector($container))->invoke($nullable);
100
101
        $this->assertNull($result);
102
    }
103
104
    /**
105
     * Nullable scalars should be set with `null` if not specified by name explicitly.
106
     */
107
    public function testWithNullableScalarArgument(): void
108
    {
109
        $container = new Container([]);
110
111
        $nullableInt = fn (?int $number) => $number;
112
113
        $result = (new Injector($container))->invoke($nullableInt);
114
115
        $this->assertNull($result);
116
    }
117
118
    /**
119
     * Optional scalar arguments should be set with default value if not specified by name explicitly.
120
     */
121
    public function testWithNullableOptionalArgument(): void
122
    {
123
        $container = new Container([]);
124
125
        $nullableInt = fn (?int $number = 6) => $number;
126
127
        $result = (new Injector($container))->invoke($nullableInt);
128
129
        $this->assertSame(6, $result);
130
    }
131
132
    /**
133
     * Optional arguments with `null` by default should be set with `null` if other value not specified in parameters
134
     * or container.
135
     */
136
    public function testWithNullableOptionalArgumentThatNull(): void
137
    {
138
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
139
140
        $callable = fn (EngineInterface $engine = null) => $engine;
141
142
        $result = (new Injector($container))->invoke($callable);
143
144
        $this->assertNotNull($result);
145
    }
146
147
    /**
148
     * An object for a typed argument can be specified in parameters without named key and without following the order.
149
     */
150
    public function testCustomDependency(): void
151
    {
152
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
153
        $needleEngine = new EngineZIL130();
154
155
        $getEngineName = fn (EngineInterface $engine) => $engine->getName();
156
157
        $engineName = (new Injector($container))->invoke(
158
            $getEngineName,
159
            [new \stdClass(), $needleEngine, new \DateTimeImmutable()]
160
        );
161
162
        $this->assertSame(EngineZIL130::NAME, $engineName);
163
    }
164
165
    /**
166
     * In this case, first argument will be set from parameters, and second argument from container.
167
     */
168
    public function testTwoEqualCustomArgumentsWithOneCustom(): void
169
    {
170
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
171
172
        $compareEngines = static function (EngineInterface $engine1, EngineInterface $engine2) {
173
            return $engine1->getPower() <=> $engine2->getPower();
174
        };
175
        $zilEngine = new EngineZIL130();
176
177
        $result = (new Injector($container))->invoke($compareEngines, [$zilEngine]);
178
179
        $this->assertSame(-1, $result);
180
    }
181
182
    /**
183
     * In this case, second argument will be set from parameters by name, and first argument from container.
184
     */
185
    public function testTwoEqualCustomArgumentsWithOneCustomNamedParameter(): void
186
    {
187
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
188
189
        $compareEngines = static function (EngineInterface $engine1, EngineInterface $engine2) {
190
            return $engine1->getPower() <=> $engine2->getPower();
191
        };
192
        $zilEngine = new EngineZIL130();
193
194
        $result = (new Injector($container))->invoke($compareEngines, ['engine2' => $zilEngine]);
195
196
        $this->assertSame(1, $result);
197
    }
198
199
    /**
200
     * Values for arguments are not matched by the greater similarity of parameter types and arguments, but simply pass
201
     * in order as is.
202
     */
203
    public function testExtendedArgumentsWithOneCustomNamedParameter2(): void
204
    {
205
        $container = new Container(
206
            [
207
                LightEngine::class => EngineVAZ2101::class,
208
            ]
209
        );
210
211
        $concatEngineNames = static function (EngineInterface $engine1, LightEngine $engine2) {
212
            return $engine1->getName() . $engine2->getName();
213
        };
214
215
        $result = (new Injector($container))->invoke($concatEngineNames, [
216
            new EngineMarkTwo(), // LightEngine, EngineInterface
217
            new EngineZIL130(), // EngineInterface
218
        ]);
219
220
        $this->assertSame(EngineMarkTwo::NAME . EngineVAZ2101::NAME, $result);
221
    }
222
223
    public function testMissingRequiredTypedParameter(): void
224
    {
225
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
226
227
        $getEngineName = static function (EngineInterface $engine, string $two) {
0 ignored issues
show
Unused Code introduced by
The parameter $two is not used and could be removed. ( Ignorable by Annotation )

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

227
        $getEngineName = static function (EngineInterface $engine, /** @scrutinizer ignore-unused */ string $two) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
228
            return $engine->getName();
229
        };
230
231
        $injector = new Injector($container);
232
233
        $this->expectException(MissingRequiredArgumentException::class);
234
        $injector->invoke($getEngineName);
235
    }
236
237
    public function testMissingRequiredNotTypedParameter(): void
238
    {
239
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
240
241
        $getEngineName = static function (EngineInterface $engine, $two) {
0 ignored issues
show
Unused Code introduced by
The parameter $two is not used and could be removed. ( Ignorable by Annotation )

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

241
        $getEngineName = static function (EngineInterface $engine, /** @scrutinizer ignore-unused */ $two) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
242
            return $engine->getName();
243
        };
244
        $injector = new Injector($container);
245
246
        $this->expectException(MissingRequiredArgumentException::class);
247
248
        $injector->invoke($getEngineName);
249
    }
250
251
    public function testNotFoundException(): void
252
    {
253
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
254
255
        $getEngineName = static function (EngineInterface $engine, ColorInterface $color) {
0 ignored issues
show
Unused Code introduced by
The parameter $color is not used and could be removed. ( Ignorable by Annotation )

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

255
        $getEngineName = static function (EngineInterface $engine, /** @scrutinizer ignore-unused */ ColorInterface $color) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
256
            return $engine->getName();
257
        };
258
259
        $injector = new Injector($container);
260
261
        $this->expectException(NotFoundExceptionInterface::class);
262
        $injector->invoke($getEngineName);
263
    }
264
265
    /**
266
     * A values collection for a variadic argument can be passed as an array in a named parameter.
267
     */
268
    public function testAloneScalarVariadicArgumentAnsNamedParam(): void
269
    {
270
        $container = new Container([]);
271
272
        $callable = fn (...$var) => array_sum($var);
273
274
        $result = (new Injector($container))->invoke($callable, ['var' => [1, 2, 3]]);
275
276
        $this->assertSame(6, $result);
277
    }
278
279
    /**
280
     * If type of a variadic argument is a class and named parameter with values collection is not set then injector
281
     * will search for objects by class name among all unnamed parameters.
282
     */
283
    public function testVariadicArgumentUnnamedParams(): void
284
    {
285
        $container = new Container([\DateTimeInterface::class => new \DateTimeImmutable()]);
286
287
        $callable = fn (\DateTimeInterface $dateTime, EngineInterface ...$engines) => count($engines);
288
289
        $result = (new Injector($container))->invoke(
290
            $callable,
291
            [new EngineZIL130(), new EngineVAZ2101(), new \stdClass(), new EngineMarkTwo(), new \stdClass()]
292
        );
293
294
        $this->assertSame(3, $result);
295
    }
296
297
    /**
298
     * If calling method have an untyped variadic argument then all remaining unnamed parameters will be passed.
299
     */
300
    public function testVariadicMixedArgumentWithMixedParams(): void
301
    {
302
        $container = new Container([\DateTimeInterface::class => new \DateTimeImmutable()]);
303
304
        $callable = fn (...$engines) => $engines;
305
306
        $result = (new Injector($container))->invoke(
307
            $callable,
308
            [new EngineZIL130(), new EngineVAZ2101(), new EngineMarkTwo(), new \stdClass()]
309
        );
310
311
        $this->assertCount(4, $result);
312
    }
313
314
    /**
315
     * Any unnamed parameter can only be an object. Scalar, array, null and other values can only be named parameters.
316
     */
317
    public function testVariadicStringArgumentWithUnnamedStringsParams(): void
318
    {
319
        $container = new Container([\DateTimeInterface::class => new \DateTimeImmutable()]);
320
321
        $callable = fn (string ...$engines) => $engines;
322
323
        $this->expectException(\Exception::class);
324
325
        (new Injector($container))->invoke($callable, ['str 1', 'str 2', 'str 3']);
326
    }
327
328
    /**
329
     * In the absence of other values to a nullable variadic argument `null` is not passed by default.
330
     */
331
    public function testNullableVariadicArgument(): void
332
    {
333
        $container = new Container([]);
334
335
        $callable = fn (?EngineInterface ...$engines) => $engines;
336
337
        $result = (new Injector($container))->invoke($callable, []);
338
339
        $this->assertSame([], $result);
340
    }
341
342
    /**
343
     * Parameters that were passed but were not used are appended to the call so they could be obtained
344
     * with func_get_args().
345
     */
346
    public function testAppendingUnusedParams(): void
347
    {
348
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
349
350
        $callable = fn (?EngineInterface $engine, $id = 'test') => func_num_args();
0 ignored issues
show
Unused Code introduced by
The parameter $engine is not used and could be removed. ( Ignorable by Annotation )

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

350
        $callable = fn (/** @scrutinizer ignore-unused */ ?EngineInterface $engine, $id = 'test') => func_num_args();

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $id is not used and could be removed. ( Ignorable by Annotation )

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

350
        $callable = fn (?EngineInterface $engine, /** @scrutinizer ignore-unused */ $id = 'test') => func_num_args();

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
351
352
        $result = (new Injector($container))->invoke($callable, [new \DateTimeImmutable(), new \DateTimeImmutable()]);
353
354
        $this->assertSame(4, $result);
355
    }
356
357
    public function testWrongNamedParam(): void
358
    {
359
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
360
361
        $callable = fn (EngineInterface $engine) => $engine;
362
363
        $this->expectException(\Throwable::class);
364
365
        (new Injector($container))->invoke($callable, ['engine' => new \DateTimeImmutable()]);
366
    }
367
368
    public function testArrayArgumentWithUnnamedType(): void
369
    {
370
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
371
372
        $callable = fn (array $arg) => $arg;
373
374
        $this->expectException(MissingRequiredArgumentException::class);
375
376
        (new Injector($container))->invoke($callable, [['test']]);
377
    }
378
379
    public function testCallableArgumentWithUnnamedType(): void
380
    {
381
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
382
383
        $callable = fn (callable $arg) => $arg();
384
385
        $this->expectException(MissingRequiredArgumentException::class);
386
387
        (new Injector($container))->invoke($callable, [fn () => true]);
388
    }
389
390
    public function testIterableArgumentWithUnnamedType(): void
391
    {
392
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
393
394
        $callable = fn (iterable $arg) => $arg;
395
396
        $this->expectException(MissingRequiredArgumentException::class);
397
398
        (new Injector($container))->invoke($callable, [new \SplStack()]);
399
    }
400
401
    public function testUnnamedScalarParam(): void
402
    {
403
        $container = new Container([]);
404
405
        $getEngineName = fn () => 42;
406
407
        $this->expectException(InvalidArgumentException::class);
408
409
        (new Injector($container))->invoke($getEngineName, ['test']);
410
    }
411
}
412