Passed
Push — develop ( c2177f...c2cb8f )
by nguereza
02:06
created

PlatineTestCase::setClassCreateObjectMaps()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Platine Dev Tools
5
 *
6
 * Platine Dev Tools is a collection of some classes/functions
7
 * designed for development
8
 *
9
 * This content is released under the MIT License (MIT)
10
 *
11
 * Copyright (c) 2020 Platine Dev Tools
12
 *
13
 * Permission is hereby granted, free of charge, to any person obtaining a copy
14
 * of this software and associated documentation files (the "Software"), to deal
15
 * in the Software without restriction, including without limitation the rights
16
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
 * copies of the Software, and to permit persons to whom the Software is
18
 * furnished to do so, subject to the following conditions:
19
 *
20
 * The above copyright notice and this permission notice shall be included in all
21
 * copies or substantial portions of the Software.
22
 *
23
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
 * SOFTWARE.
30
 */
31
32
/**
33
 *  @file PlatineTestCase.php
34
 *
35
 *  The Base class used for test case
36
 *
37
 *  @package    Platine\Dev
38
 *  @author Platine Developers Team
39
 *  @copyright  Copyright (c) 2020
40
 *  @license    http://opensource.org/licenses/MIT  MIT License
41
 *  @link   https://www.platine-php.com
42
 *  @version 1.0.0
43
 *  @filesource
44
 */
45
46
declare(strict_types=1);
47
48
namespace Platine\Dev;
49
50
use InvalidArgumentException;
51
use org\bovigo\vfs\vfsStream;
52
use org\bovigo\vfs\vfsStreamContainer;
53
use org\bovigo\vfs\vfsStreamDirectory;
54
use org\bovigo\vfs\vfsStreamFile;
55
use PHPUnit\Framework\TestCase;
56
use ReflectionClass;
57
use ReflectionNamedType;
58
use ReflectionParameter;
59
use ReflectionProperty;
60
61
/**
62
 * @class PlatineTestCase
63
 * @package Platine\Dev
64
 */
65
class PlatineTestCase extends TestCase
66
{
67
    /**
68
     * The class create object maps
69
     * @var array<class-string, array<string, mixed>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<class-string, array<string, mixed>> at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in array<class-string, array<string, mixed>>.
Loading history...
70
     */
71
    protected array $createObjectMaps = [];
72
73
74
    /**
75
     * Set create object map
76
     * @param class-string $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
77
     * @param array<string, mixed> $maps
78
     * @return $this
79
     */
80
    public function setClassCreateObjectMaps(string $class, array $maps): self
81
    {
82
        $this->createObjectMaps[$class] = $maps;
83
84
        return $this;
85
    }
86
87
    /**
88
     * Create object
89
     * @param class-string $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
90
     * @return object|null
91
     */
92
    public function createObject(string $class): ?object
93
    {
94
        $rc = new ReflectionClass($class);
95
96
        if ($rc->isInstantiable() === false) {
97
            return null;
98
        }
99
100
        $constructor = $rc->getConstructor();
101
102
        if ($constructor === null) {
103
            return $rc->newInstanceWithoutConstructor();
104
        }
105
106
        $arguments = [];
107
        $parameters = $constructor->getParameters();
108
        $maps = $this->createObjectMaps[$class] ?? [];
109
        foreach ($parameters as /** @var ReflectionParameter $parameter */ $parameter) {
110
            $name = $parameter->getName();
111
            if (array_key_exists($name, $maps)) {
112
                $arguments[] = $maps[$name];
113
                continue;
114
            }
115
116
            /** @var ReflectionNamedType $type */
117
            $type = $parameter->getType();
118
            $value = null;
119
            if ($type !== null && $type->isBuiltin() === false) {
120
                // Create Mock
121
                $className = $type->getName();
122
                $value = $this->getMockInstance($className);
123
            }
124
125
            if ($value === null) {
126
                if ($parameter->isDefaultValueAvailable()) {
127
                    $value = $parameter->getDefaultValue();
128
                }
129
            }
130
131
            $arguments[] = $value;
132
        }
133
134
        return $rc->newInstanceArgs($arguments);
135
    }
136
137
    /**
138
     * Test object method call count
139
     * @param object $object
140
     * @param string $method
141
     * @param int $count
142
     * @return void
143
     */
144
    public function expectMethodCallCount(object $object, string $method, int $count = 1): void
145
    {
146
        $object->expects($this->exactly($count))
147
                ->method($method);
148
    }
149
150
    /**
151
     * Method to test private & protected method
152
     *
153
     * @param object $object the class instance to use
154
     * @param string $method the name of the method
155
     * @param array<int, mixed> $args the list of method arguments
156
     * @return mixed
157
     */
158
    public function runPrivateProtectedMethod(
159
        object $object,
160
        string $method,
161
        array $args = []
162
    ) {
163
        $reflection = new ReflectionClass(get_class($object));
164
        $reflectionMethod = $reflection->getMethod($method);
165
        $reflectionMethod->setAccessible(true);
166
        return $reflectionMethod->invokeArgs($object, $args);
167
    }
168
169
    /**
170
     * Method to set/get private & protected attribute
171
     *
172
     * @param string $className the name of the class
173
     * @param string $attr the name of the class attribute
174
     */
175
    public function getPrivateProtectedAttribute(
176
        string $className,
177
        string $attr
178
    ): ReflectionProperty {
179
        $rProp = new ReflectionProperty($className, $attr);
180
        $rProp->setAccessible(true);
181
        return $rProp;
182
    }
183
184
    /**
185
     * Create virtual file with the given content
186
     * @param  string $filename
187
     * @param  vfsStreamContainer<vfsStreamContainerIterator> $destination
188
     * @param  string $content
189
     * @return vfsStreamFile
190
     */
191
    public function createVfsFile(
192
        string $filename,
193
        vfsStreamContainer $destination,
194
        string $content = ''
195
    ): vfsStreamFile {
196
        return vfsStream::newFile($filename)
197
                        ->at($destination)
198
                        ->setContent($content);
199
    }
200
201
    /**
202
     * Create virtual file without content
203
     * @param  string $filename
204
     * @param  vfsStreamContainer<vfsStreamContainerIterator> $destination
205
     * @return vfsStreamFile
206
     */
207
    public function createVfsFileOnly(
208
        string $filename,
209
        vfsStreamContainer $destination
210
    ): vfsStreamFile {
211
        return vfsStream::newFile($filename)
212
                        ->at($destination);
213
    }
214
215
    /**
216
     * Create virtual directory
217
     * @param  string $name
218
     * @param  vfsStreamContainer<vfsStreamContainerIterator> $destination
219
     * @return vfsStreamDirectory
220
     */
221
    public function createVfsDirectory(
222
        string $name,
223
        vfsStreamContainer $destination = null
224
    ): vfsStreamDirectory {
225
        if ($destination) {
226
            return vfsStream::newDirectory($name)->at($destination);
227
        }
228
        return vfsStream::newDirectory($name);
229
    }
230
231
    /**
232
     * Return the list of methods to mocks in the parameters of PHPUnit::TestCase::getMock()
233
     *
234
     * @param class-string<object>|object $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<object>|object at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<object>|object.
Loading history...
235
     * @param string[] $exclude list of methods to exclude
236
     * @return string[]
237
     */
238
    public function getClassMethodsToMock($class, array $exclude = []): array
239
    {
240
        $methods = [];
241
242
        if (is_string($class) && !class_exists($class)) {
243
            throw new InvalidArgumentException(
244
                sprintf('Can not find class [%s]', $class)
245
            );
246
        }
247
248
        $reflectionClass = new ReflectionClass($class);
249
250
        foreach ($reflectionClass->getMethods() as $reflectionMethod) {
251
            if (!in_array($reflectionMethod->name, $exclude)) {
252
                $methods[] = $reflectionMethod->name;
253
            }
254
        }
255
256
        return $methods;
257
    }
258
259
    /**
260
     * Get the instance of the given class
261
     * @param class-string $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
262
     * @param array<string, mixed> $mockMethods
263
     * @param array<int, string> $excludes
264
     * @return mixed
265
     */
266
    public function getMockInstance(
267
        string $class,
268
        array $mockMethods = [],
269
        array $excludes = []
270
    ) {
271
        $methods = $this->getClassMethodsToMock($class, $excludes);
272
273
        $mock = $this->getMockBuilder($class)
274
                    ->disableOriginalConstructor()
275
                    ->onlyMethods($methods)
276
                    ->getMock();
277
278
        foreach ($mockMethods as $method => $returnValue) {
279
            $mock->expects($this->any())
280
                ->method($method)
281
                ->will($this->returnValue($returnValue));
282
        }
283
284
        return $mock;
285
    }
286
287
    /**
288
     * Get the instance of the given class using return map
289
     * @param class-string $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
290
     * @param array<string, mixed> $mockMethods
291
     * @param array<int, string> $excludes
292
     * @return mixed
293
     */
294
    public function getMockInstanceMap(
295
        string $class,
296
        array $mockMethods = [],
297
        array $excludes = []
298
    ) {
299
        $methods = $this->getClassMethodsToMock($class, $excludes);
300
301
        $mock = $this->getMockBuilder($class)
302
                    ->disableOriginalConstructor()
303
                    ->onlyMethods($methods)
304
                    ->getMock();
305
306
        foreach ($mockMethods as $method => $returnValues) {
307
            $mock->expects($this->any())
308
                ->method($method)
309
                ->will(
310
                    $this->returnValueMap($returnValues)
311
                );
312
        }
313
314
        return $mock;
315
    }
316
317
    /**
318
     * Return the value of private or protected property
319
     * @param class-string $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
320
     * @param object $instance
321
     * @param string $name
322
     * @return mixed
323
     */
324
    public function getPropertyValue(string $class, object $instance, string $name)
325
    {
326
        $reflection = $this->getPrivateProtectedAttribute($class, $name);
327
        return $reflection->getValue($instance);
328
    }
329
330
    /**
331
     * Set the value of private or protected property
332
     * @param class-string $class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string.
Loading history...
333
     * @param object $instance
334
     * @param string $name
335
     * @param mixed $value
336
     * @return void
337
     */
338
    public function setPropertyValue(string $class, object $instance, string $name, $value)
339
    {
340
        $reflection = $this->getPrivateProtectedAttribute($class, $name);
341
        $reflection->setValue($instance, $value);
342
    }
343
344
    /**
345
     * Test assert command expected given output
346
     * @param string $expected
347
     * @param string $output
348
     * @return void
349
     */
350
    public function assertCommandOutput(string $expected, string $output): void
351
    {
352
        $formattedExpected = preg_replace('~\r\n?~', "\n", $expected);
353
        $formattedOutput = preg_replace('~\r\n?~', "\n", $output);
354
355
        $this->assertEquals($formattedExpected, $formattedOutput);
356
    }
357
358
    /**
359
     * @codeCoverageIgnore
360
     * @return void
361
     */
362
    protected function tearDown(): void
363
    {
364
        //restore all mock variable global value to "false"
365
        foreach ($GLOBALS as $key => $value) {
366
            if (substr((string) $key, 0, 5) === 'mock_') {
367
                $GLOBALS[$key] = false;
368
            }
369
        }
370
    }
371
}
372