Passed
Pull Request — master (#3)
by
unknown
01:19
created

testVariadicStringArgumentWithUnnamedStringsParams()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

221
        $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...
222
            return $engine->getName();
223
        };
224
225
        $injector = new Injector($container);
226
227
        $this->expectException(MissingRequiredArgumentException::class);
228
        $injector->invoke($getEngineName);
229
    }
230
231
    public function testMissingRequiredNotTypedParameter(): void
232
    {
233
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
234
235
        $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

235
        $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...
236
            return $engine->getName();
237
        };
238
        $injector = new Injector($container);
239
240
        $this->expectException(MissingRequiredArgumentException::class);
241
242
        $injector->invoke($getEngineName);
243
    }
244
245
    public function testNotFoundException(): void
246
    {
247
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
248
249
        $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

249
        $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...
250
            return $engine->getName();
251
        };
252
253
        $injector = new Injector($container);
254
255
        $this->expectException(NotFoundExceptionInterface::class);
256
        $injector->invoke($getEngineName);
257
    }
258
259
    /**
260
     * A values collection for a variadic argument can be passed as an array in a named parameter
261
     */
262
    public function testAloneScalarVariadicArgumentAnsNamedParam(): void
263
    {
264
        $container = new Container([]);
265
266
        $callable = fn (...$var) => array_sum($var);
267
268
        $result = (new Injector($container))->invoke($callable, ['var' => [1, 2, 3]]);
269
270
        $this->assertSame(6, $result);
271
    }
272
273
    /**
274
     * If type of a variadic argument is a class and named parameter with values collection is not set then injector
275
     * will search for objects by class name among all unnamed parameters
276
     */
277
    public function testVariadicArgumentUnnamedParams(): void
278
    {
279
        $container = new Container([\DateTimeInterface::class => new \DateTimeImmutable()]);
280
281
        $callable = fn (\DateTimeInterface $dateTime, EngineInterface ...$engines) => count($engines);
282
283
        $result = (new Injector($container))->invoke(
284
            $callable,
285
            [new EngineZIL130(), new EngineVAZ2101(), new \stdClass(), new EngineMarkTwo(), new \stdClass()]
286
        );
287
288
        $this->assertSame(3, $result);
289
    }
290
291
    /**
292
     * If calling method have an untyped variadic argument then all remaining unnamed parameters will be passed
293
     */
294
    public function testVariadicMixedArgumentWithMixedParams(): void
295
    {
296
        $container = new Container([\DateTimeInterface::class => new \DateTimeImmutable()]);
297
298
        $callable = fn (...$engines) => $engines;
299
300
        $result = (new Injector($container))->invoke(
301
            $callable,
302
            [new EngineZIL130(), new EngineVAZ2101(), new EngineMarkTwo(), new \stdClass()]
303
        );
304
305
        $this->assertCount(4, $result);
306
    }
307
308
    /**
309
     * Any unnamed parameter can only be an object. Scalar, array, null and other values can only be named parameters
310
     */
311
    public function testVariadicStringArgumentWithUnnamedStringsParams(): void
312
    {
313
        $container = new Container([\DateTimeInterface::class => new \DateTimeImmutable()]);
314
315
        $callable = fn (string ...$engines) => $engines;
316
317
        $this->expectException(\Exception::class);
318
319
        (new Injector($container))->invoke($callable, ['str 1', 'str 2', 'str 3']);
320
    }
321
322
    /**
323
     * In the absence of other values to a nullable variadic argument `null` is not passed by default
324
     */
325
    public function testNullableVariadicArgument(): void
326
    {
327
        $container = new Container([]);
328
329
        $callable = fn (?EngineInterface ...$engines) => $engines;
330
331
        $result = (new Injector($container))->invoke($callable, []);
332
333
        $this->assertSame([], $result);
334
    }
335
336
    public function testAppendingUnusedParams(): void
337
    {
338
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
339
340
        $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

340
        $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

340
        $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...
341
342
        $result = (new Injector($container))->invoke($callable, [new \DateTimeImmutable(), new \DateTimeImmutable()]);
343
344
        $this->assertSame(4, $result);
345
    }
346
347
    public function testWrongNamedParam(): void
348
    {
349
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
350
351
        $callable = fn (EngineInterface $engine) => $engine;
352
353
        $this->expectException(\Throwable::class);
354
355
        (new Injector($container))->invoke($callable, ['engine' => new \DateTimeImmutable()]);
356
    }
357
358
    public function testArrayArgumentWithUnnamedType(): void
359
    {
360
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
361
362
        $callable = fn (array $arg) => $arg;
363
364
        $this->expectException(MissingRequiredArgumentException::class);
365
366
        (new Injector($container))->invoke($callable, [['test']]);
367
    }
368
369
    public function testCallableArgumentWithUnnamedType(): void
370
    {
371
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
372
373
        $callable = fn (callable $arg) => $arg();
374
375
        $this->expectException(MissingRequiredArgumentException::class);
376
377
        (new Injector($container))->invoke($callable, [fn () => true]);
378
    }
379
380
    public function testIterableArgumentWithUnnamedType(): void
381
    {
382
        $container = new Container([EngineInterface::class => EngineMarkTwo::class]);
383
384
        $callable = fn (iterable $arg) => $arg;
385
386
        $this->expectException(MissingRequiredArgumentException::class);
387
388
        (new Injector($container))->invoke($callable, [new \SplStack()]);
389
    }
390
391
    public function testUnnamedScalarParam(): void
392
    {
393
        $container = new Container([]);
394
395
        $getEngineName = fn () => 42;
396
397
        $this->expectException(InvalidParameterException::class);
398
399
        (new Injector($container))->invoke($getEngineName, ['test']);
400
    }
401
}
402