Passed
Pull Request — master (#1)
by Kevin
02:47
created

InstantiatorTest::can_use_always_force_mode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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