Passed
Pull Request — master (#81)
by Kevin
32:57 queued 13:01
created

extra_attributes_not_defined_throws_exception()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
c 1
b 0
f 0
dl 0
loc 10
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Zenstruck\Foundry\Tests\Unit;
4
5
use PHPUnit\Framework\TestCase;
0 ignored issues
show
Bug introduced by
The type PHPUnit\Framework\TestCase was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait;
7
use Zenstruck\Foundry\Instantiator;
8
9
/**
10
 * @author Kevin Bond <[email protected]>
11
 */
12
final class InstantiatorTest extends TestCase
13
{
14
    use ExpectDeprecationTrait;
15
16
    /**
17
     * @test
18
     */
19
    public function default_instantiate(): void
20
    {
21
        $object = (new Instantiator())([
22
            'propA' => 'A',
23
            'propB' => 'B',
24
            'propC' => 'C',
25
            'propD' => 'D',
26
        ], InstantiatorDummy::class);
27
28
        $this->assertSame('A', $object->propA);
29
        $this->assertSame('A', $object->getPropA());
30
        $this->assertSame('constructor B', $object->getPropB());
31
        $this->assertSame('constructor C', $object->getPropC());
32
        $this->assertSame('setter D', $object->getPropD());
33
    }
34
35
    /**
36
     * @test
37
     */
38
    public function can_use_snake_case_attributes(): void
39
    {
40
        $object = (new Instantiator())([
41
            'prop_a' => 'A',
42
            'prop_b' => 'B',
43
            'prop_c' => 'C',
44
            'prop_d' => 'D',
45
        ], InstantiatorDummy::class);
46
47
        $this->assertSame('A', $object->propA);
48
        $this->assertSame('A', $object->getPropA());
49
        $this->assertSame('constructor B', $object->getPropB());
50
        $this->assertSame('constructor C', $object->getPropC());
51
        $this->assertSame('setter D', $object->getPropD());
52
    }
53
54
    /**
55
     * @test
56
     */
57
    public function can_use_kebab_case_attributes(): void
58
    {
59
        $object = (new Instantiator())([
60
            'prop-a' => 'A',
61
            'prop-b' => 'B',
62
            'prop-c' => 'C',
63
            'prop-d' => 'D',
64
        ], InstantiatorDummy::class);
65
66
        $this->assertSame('A', $object->propA);
67
        $this->assertSame('A', $object->getPropA());
68
        $this->assertSame('constructor B', $object->getPropB());
69
        $this->assertSame('constructor C', $object->getPropC());
70
        $this->assertSame('setter D', $object->getPropD());
71
    }
72
73
    /**
74
     * @test
75
     */
76
    public function can_leave_off_default_constructor_argument(): void
77
    {
78
        $object = (new Instantiator())([
79
            'propB' => 'B',
80
        ], InstantiatorDummy::class);
81
82
        $this->assertSame('constructor B', $object->getPropB());
83
        $this->assertNull($object->getPropC());
84
    }
85
86
    /**
87
     * @test
88
     */
89
    public function can_instantiate_object_with_private_constructor(): void
90
    {
91
        $object = (new Instantiator())([
92
            'propA' => 'A',
93
            'propB' => 'B',
94
            'propC' => 'C',
95
            'propD' => 'D',
96
        ], PrivateConstructorInstantiatorDummy::class);
97
98
        $this->assertSame('A', $object->propA);
99
        $this->assertSame('A', $object->getPropA());
100
        $this->assertSame('setter B', $object->getPropB());
101
        $this->assertSame('setter C', $object->getPropC());
102
        $this->assertSame('setter D', $object->getPropD());
103
    }
104
105
    /**
106
     * @test
107
     */
108
    public function missing_constructor_argument_throws_exception(): void
109
    {
110
        $this->expectException(\InvalidArgumentException::class);
111
        $this->expectExceptionMessage('Missing constructor argument "propB" for "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy".');
112
113
        (new Instantiator())([], InstantiatorDummy::class);
114
    }
115
116
    /**
117
     * @test
118
     */
119
    public function extra_attributes_throws_exception(): void
120
    {
121
        $this->expectException(\InvalidArgumentException::class);
122
        $this->expectExceptionMessage('Cannot set attribute "extra" for object "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy" (not public and no setter).');
123
124
        (new Instantiator())([
125
            'propB' => 'B',
126
            'extra' => 'foo',
127
        ], InstantiatorDummy::class);
128
    }
129
130
    /**
131
     * @test
132
     */
133
    public function can_set_attributes_that_should_be_optional(): void
134
    {
135
        $object = (new Instantiator())->allowExtraAttributes('extra')([
136
            'propB' => 'B',
137
            'extra' => 'foo',
138
        ], InstantiatorDummy::class);
139
140
        $this->assertSame('constructor B', $object->getPropB());
141
    }
142
143
    /**
144
     * @test
145
     */
146
    public function extra_attributes_not_defined_throws_exception(): void
147
    {
148
        $this->expectException(\InvalidArgumentException::class);
149
        $this->expectExceptionMessage('Cannot set attribute "extra2" for object "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy" (not public and no setter).');
150
151
        (new Instantiator())->allowExtraAttributes('extra1')([
152
            'propB' => 'B',
153
            'extra1' => 'foo',
154
            'extra2' => 'bar',
155
        ], InstantiatorDummy::class);
156
    }
157
158
    /**
159
     * @test
160
     * @group legacy
161
     */
162
    public function can_prefix_extra_attribute_key_with_optional_to_avoid_exception(): void
163
    {
164
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using "optional:" attribute prefixes is deprecated, use Instantiator::allowExtraAttributes() instead (https://github.com/zenstruck/foundry#instantiation).');
165
166
        $object = (new Instantiator())([
167
            'propB' => 'B',
168
            'optional:extra' => 'foo',
169
        ], InstantiatorDummy::class);
170
171
        $this->assertSame('constructor B', $object->getPropB());
172
    }
173
174
    /**
175
     * @test
176
     */
177
    public function can_always_allow_extra_attributes(): void
178
    {
179
        $object = (new Instantiator())->allowExtraAttributes()([
180
            'propB' => 'B',
181
            'extra' => 'foo',
182
        ], InstantiatorDummy::class);
183
184
        $this->assertSame('constructor B', $object->getPropB());
185
    }
186
187
    /**
188
     * @test
189
     */
190
    public function can_disable_constructor(): void
191
    {
192
        $object = (new Instantiator())->withoutConstructor()([
193
            'propA' => 'A',
194
            'propB' => 'B',
195
            'propC' => 'C',
196
            'propD' => 'D',
197
        ], InstantiatorDummy::class);
198
199
        $this->assertSame('A', $object->propA);
200
        $this->assertSame('A', $object->getPropA());
201
        $this->assertSame('setter B', $object->getPropB());
202
        $this->assertSame('setter C', $object->getPropC());
203
        $this->assertSame('setter D', $object->getPropD());
204
    }
205
206
    /**
207
     * @test
208
     */
209
    public function can_set_attributes_that_should_be_force_set(): void
210
    {
211
        $object = (new Instantiator())->withoutConstructor()->alwaysForceProperties('propD')([
212
            'propB' => 'B',
213
            'propD' => 'D',
214
        ], InstantiatorDummy::class);
215
216
        $this->assertSame('setter B', $object->getPropB());
217
        $this->assertSame('D', $object->getPropD());
218
    }
219
220
    /**
221
     * @test
222
     * @group legacy
223
     */
224
    public function prefixing_attribute_key_with_force_sets_the_property_directly(): void
225
    {
226
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using "force:" property prefixes is deprecated, use Instantiator::alwaysForceProperties() instead (https://github.com/zenstruck/foundry#instantiation).');
227
228
        $object = (new Instantiator())([
229
            'propA' => 'A',
230
            'propB' => 'B',
231
            'propC' => 'C',
232
            'force:propD' => 'D',
233
        ], InstantiatorDummy::class);
234
235
        $this->assertSame('A', $object->propA);
236
        $this->assertSame('A', $object->getPropA());
237
        $this->assertSame('constructor B', $object->getPropB());
238
        $this->assertSame('constructor C', $object->getPropC());
239
        $this->assertSame('D', $object->getPropD());
240
    }
241
242
    /**
243
     * @test
244
     * @group legacy
245
     */
246
    public function prefixing_snake_case_attribute_key_with_force_sets_the_property_directly(): void
247
    {
248
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using "force:" property prefixes is deprecated, use Instantiator::alwaysForceProperties() instead (https://github.com/zenstruck/foundry#instantiation).');
249
250
        $object = (new Instantiator())([
251
            'prop_a' => 'A',
252
            'prop_b' => 'B',
253
            'prop_c' => 'C',
254
            'force:prop_d' => 'D',
255
        ], InstantiatorDummy::class);
256
257
        $this->assertSame('A', $object->propA);
258
        $this->assertSame('A', $object->getPropA());
259
        $this->assertSame('constructor B', $object->getPropB());
260
        $this->assertSame('constructor C', $object->getPropC());
261
        $this->assertSame('D', $object->getPropD());
262
    }
263
264
    /**
265
     * @test
266
     * @group legacy
267
     */
268
    public function prefixing_kebab_case_attribute_key_with_force_sets_the_property_directly(): void
269
    {
270
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using "force:" property prefixes is deprecated, use Instantiator::alwaysForceProperties() instead (https://github.com/zenstruck/foundry#instantiation).');
271
272
        $object = (new Instantiator())([
273
            'prop-a' => 'A',
274
            'prop-b' => 'B',
275
            'prop-c' => 'C',
276
            'force:prop-d' => 'D',
277
        ], InstantiatorDummy::class);
278
279
        $this->assertSame('A', $object->propA);
280
        $this->assertSame('A', $object->getPropA());
281
        $this->assertSame('constructor B', $object->getPropB());
282
        $this->assertSame('constructor C', $object->getPropC());
283
        $this->assertSame('D', $object->getPropD());
284
    }
285
286
    /**
287
     * @test
288
     * @group legacy
289
     */
290
    public function prefixing_invalid_attribute_key_with_force_throws_exception(): void
291
    {
292
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using "force:" property prefixes is deprecated, use Instantiator::alwaysForceProperties() instead (https://github.com/zenstruck/foundry#instantiation).');
293
        $this->expectException(\InvalidArgumentException::class);
294
        $this->expectExceptionMessage('Class "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy" does not have property "extra".');
295
296
        (new Instantiator())([
297
            'propB' => 'B',
298
            'force:extra' => 'foo',
299
        ], InstantiatorDummy::class);
300
    }
301
302
    /**
303
     * @test
304
     */
305
    public function can_use_force_set_and_get(): void
306
    {
307
        $object = new InstantiatorDummy('B');
308
309
        $this->assertNull(Instantiator::forceGet($object, 'propE'));
310
        $this->assertNull(Instantiator::forceGet($object, 'prop_e'));
311
        $this->assertNull(Instantiator::forceGet($object, 'prop-e'));
312
313
        Instantiator::forceSet($object, 'propE', 'camel');
314
315
        $this->assertSame('camel', Instantiator::forceGet($object, 'propE'));
316
        $this->assertSame('camel', Instantiator::forceGet($object, 'prop_e'));
317
        $this->assertSame('camel', Instantiator::forceGet($object, 'prop-e'));
318
319
        Instantiator::forceSet($object, 'prop_e', 'snake');
320
321
        $this->assertSame('snake', Instantiator::forceGet($object, 'propE'));
322
        $this->assertSame('snake', Instantiator::forceGet($object, 'prop_e'));
323
        $this->assertSame('snake', Instantiator::forceGet($object, 'prop-e'));
324
325
        Instantiator::forceSet($object, 'prop-e', 'kebab');
326
327
        $this->assertSame('kebab', Instantiator::forceGet($object, 'propE'));
328
        $this->assertSame('kebab', Instantiator::forceGet($object, 'prop_e'));
329
        $this->assertSame('kebab', Instantiator::forceGet($object, 'prop-e'));
330
    }
331
332
    /**
333
     * @test
334
     */
335
    public function force_set_throws_exception_for_invalid_property(): void
336
    {
337
        $this->expectException(\InvalidArgumentException::class);
338
        $this->expectExceptionMessage('Class "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy" does not have property "invalid".');
339
340
        Instantiator::forceSet(new InstantiatorDummy('B'), 'invalid', 'value');
341
    }
342
343
    /**
344
     * @test
345
     */
346
    public function force_get_throws_exception_for_invalid_property(): void
347
    {
348
        $this->expectException(\InvalidArgumentException::class);
349
        $this->expectExceptionMessage('Class "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy" does not have property "invalid".');
350
351
        Instantiator::forceGet(new InstantiatorDummy('B'), 'invalid');
352
    }
353
354
    /**
355
     * @test
356
     */
357
    public function can_use_always_force_mode(): void
358
    {
359
        $object = (new Instantiator())->alwaysForceProperties()([
360
            'propA' => 'A',
361
            'propB' => 'B',
362
            'propC' => 'C',
363
            'propD' => 'D',
364
        ], InstantiatorDummy::class);
365
366
        $this->assertSame('A', $object->propA);
367
        $this->assertSame('A', $object->getPropA());
368
        $this->assertSame('constructor B', $object->getPropB());
369
        $this->assertSame('constructor C', $object->getPropC());
370
        $this->assertSame('D', $object->getPropD());
371
    }
372
373
    /**
374
     * @test
375
     */
376
    public function can_use_always_force_mode_allows_snake_case(): void
377
    {
378
        $object = (new Instantiator())->alwaysForceProperties()([
379
            'prop_a' => 'A',
380
            'prop_b' => 'B',
381
            'prop_c' => 'C',
382
            'prop_d' => 'D',
383
        ], InstantiatorDummy::class);
384
385
        $this->assertSame('A', $object->propA);
386
        $this->assertSame('A', $object->getPropA());
387
        $this->assertSame('constructor B', $object->getPropB());
388
        $this->assertSame('constructor C', $object->getPropC());
389
        $this->assertSame('D', $object->getPropD());
390
    }
391
392
    /**
393
     * @test
394
     */
395
    public function can_use_always_force_mode_allows_kebab_case(): void
396
    {
397
        $object = (new Instantiator())->alwaysForceProperties()([
398
            'prop-a' => 'A',
399
            'prop-b' => 'B',
400
            'prop-c' => 'C',
401
            'prop-d' => 'D',
402
        ], InstantiatorDummy::class);
403
404
        $this->assertSame('A', $object->propA);
405
        $this->assertSame('A', $object->getPropA());
406
        $this->assertSame('constructor B', $object->getPropB());
407
        $this->assertSame('constructor C', $object->getPropC());
408
        $this->assertSame('D', $object->getPropD());
409
    }
410
411
    /**
412
     * @test
413
     */
414
    public function always_force_mode_throws_exception_for_extra_attributes(): void
415
    {
416
        $this->expectException(\InvalidArgumentException::class);
417
        $this->expectExceptionMessage('Class "Zenstruck\Foundry\Tests\Unit\InstantiatorDummy" does not have property "extra".');
418
419
        (new Instantiator())->alwaysForceProperties()([
420
            'propB' => 'B',
421
            'extra' => 'foo',
422
        ], InstantiatorDummy::class);
423
    }
424
425
    /**
426
     * @test
427
     * @group legacy
428
     */
429
    public function always_force_mode_allows_optional_attribute_name_prefix(): void
430
    {
431
        $this->expectDeprecation('Since zenstruck\foundry 1.5.0: Using "optional:" attribute prefixes is deprecated, use Instantiator::allowExtraAttributes() instead (https://github.com/zenstruck/foundry#instantiation).');
432
433
        $object = (new Instantiator())->alwaysForceProperties()([
434
            'propB' => 'B',
435
            'propD' => 'D',
436
            'optional:extra' => 'foo',
437
        ], InstantiatorDummy::class);
438
439
        $this->assertSame('D', $object->getPropD());
440
    }
441
442
    /**
443
     * @test
444
     */
445
    public function always_force_mode_with_allow_extra_attributes_mode(): void
446
    {
447
        $object = (new Instantiator())->allowExtraAttributes()->alwaysForceProperties()([
448
            'propB' => 'B',
449
            'propD' => 'D',
450
            'extra' => 'foo',
451
        ], InstantiatorDummy::class);
452
453
        $this->assertSame('D', $object->getPropD());
454
    }
455
456
    /**
457
     * @test
458
     */
459
    public function always_force_mode_can_set_parent_class_properties(): void
460
    {
461
        $object = (new Instantiator())->alwaysForceProperties()([
462
            'propA' => 'A',
463
            'propB' => 'B',
464
            'propC' => 'C',
465
            'propD' => 'D',
466
            'propE' => 'E',
467
        ], ExtendedInstantiatorDummy::class);
468
469
        $this->assertSame('A', $object->propA);
470
        $this->assertSame('A', $object->getPropA());
471
        $this->assertSame('constructor B', $object->getPropB());
472
        $this->assertSame('constructor C', $object->getPropC());
473
        $this->assertSame('D', $object->getPropD());
474
        $this->assertSame('E', Instantiator::forceGet($object, 'propE'));
475
    }
476
477
    /**
478
     * @test
479
     */
480
    public function invalid_attribute_type_with_allow_extra_enabled_throws_exception(): void
481
    {
482
        $this->expectException(\Throwable::class);
483
484
        (new Instantiator())->allowExtraAttributes()([
485
            'propB' => 'B',
486
            'propF' => 'F',
487
        ], InstantiatorDummy::class);
488
    }
489
}
490
491
class InstantiatorDummy
492
{
493
    public $propA;
494
    public $propD;
495
    private $propB;
496
    private $propC;
497
    private $propE;
0 ignored issues
show
introduced by
The private property $propE is not used, and could be removed.
Loading history...
498
    private $propF;
499
500
    public function __construct($propB, $propC = null)
501
    {
502
        $this->propB = 'constructor '.$propB;
503
504
        if ($propC) {
505
            $this->propC = 'constructor '.$propC;
506
        }
507
    }
508
509
    public function getPropA()
510
    {
511
        return $this->propA;
512
    }
513
514
    public function getPropB()
515
    {
516
        return $this->propB;
517
    }
518
519
    public function setPropB($propB)
520
    {
521
        $this->propB = 'setter '.$propB;
522
    }
523
524
    public function getPropC()
525
    {
526
        return $this->propC;
527
    }
528
529
    public function setPropC($propC)
530
    {
531
        $this->propC = 'setter '.$propC;
532
    }
533
534
    public function getPropD()
535
    {
536
        return $this->propD;
537
    }
538
539
    public function setPropD($propD)
540
    {
541
        $this->propD = 'setter '.$propD;
542
    }
543
544
    public function setPropF(object $propF)
545
    {
546
        $this->propF = $propF;
547
    }
548
}
549
550
class ExtendedInstantiatorDummy extends InstantiatorDummy
551
{
552
}
553
554
class PrivateConstructorInstantiatorDummy extends InstantiatorDummy
555
{
556
    private function __construct()
557
    {
558
        parent::__construct('B', 'C');
559
    }
560
}
561