Skip to content

Commit f34073e

Browse files
committed
Add PHPUnit tests - for 100% code coverage 🎉
1 parent 8719f32 commit f34073e

18 files changed

Lines changed: 2811 additions & 2 deletions

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/tests export-ignore
12
/.gitattributes export-ignore
23
/.gitignore export-ignore
34

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ desktop.ini
1313
# Composer
1414
/composer.lock
1515
/vendor
16+
17+
# PHPUnit
18+
/tests/.phpunit.result.cache
19+
/tests/clover.xml

composer.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,16 @@
2626
}
2727
},
2828
"require": {
29-
"php": ">=7.2.0",
29+
"php": "^7.2 || ^8.0",
30+
"phrozenbyte/phpunit-throwable-asserts": "^1.0",
3031
"phplucidframe/console-table": "^1.2"
3132
},
33+
"conflict": {
34+
"phpunit/phpunit": "<8.0"
35+
},
3236
"require-dev": {
33-
"phpunit/phpunit": "^8"
37+
"phpunit/phpunit": "^8",
38+
"mockery/mockery": "^1.3",
39+
"symfony/yaml": "^5.2"
3440
}
3541
}

tests/TestCase.php

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
<?php
2+
/**
3+
* PHPUnitArrayAssertions - Array-related PHPUnit assertions for API testing.
4+
*
5+
* @copyright Copyright (c) 2021, Daniel Rudolf (<https://www.daniel-rudolf.de>)
6+
*
7+
* This file is copyrighted by the contributors recorded in the version control
8+
* history of the file, available from the following original location:
9+
*
10+
* <https://github.com/PhrozenByte/phpunit-array-asserts/blob/master/tests/TestCase.php>
11+
*
12+
* @license http://opensource.org/licenses/MIT The MIT License
13+
*
14+
* SPDX-License-Identifier: MIT
15+
* License-Filename: LICENSE
16+
*/
17+
18+
declare(strict_types=1);
19+
20+
namespace PhrozenByte\PHPUnitArrayAsserts\Tests;
21+
22+
use Generator;
23+
use PHPUnit\Framework\Constraint\Constraint;
24+
use PHPUnit\Framework\Exception as PHPUnitException;
25+
use PHPUnit\Framework\MockObject\MockObject;
26+
use PHPUnit\Framework\MockObject\Rule\InvocationOrder;
27+
use PhrozenByte\PHPUnitThrowableAsserts\ThrowableAssertsTrait;
28+
use ReflectionObject;
29+
use Symfony\Component\Yaml\Exception\ParseException as YamlParseException;
30+
use Symfony\Component\Yaml\Yaml;
31+
32+
abstract class TestCase extends \PHPUnit\Framework\TestCase
33+
{
34+
use ThrowableAssertsTrait;
35+
36+
/** @var array[][][] */
37+
protected static $testDataSets = [];
38+
39+
/**
40+
* @param Constraint $constraint
41+
* @param InvocationOrder[] $invocationRules
42+
* @param mixed[]|null $evaluateParameters
43+
*
44+
* @return Constraint
45+
*/
46+
protected function mockConstraint(
47+
Constraint $constraint,
48+
array $invocationRules = [],
49+
array $evaluateParameters = null
50+
): Constraint {
51+
/** @var Constraint|MockObject $mockedConstraint */
52+
$mockedConstraint = $this->getMockBuilder(get_class($constraint))
53+
->disableOriginalConstructor()
54+
->disableOriginalClone()
55+
->disableArgumentCloning()
56+
->disallowMockingUnknownTypes()
57+
->disableAutoReturnValueGeneration()
58+
->onlyMethods([ 'toString', 'evaluate', 'count' ])
59+
->getMock();
60+
61+
$mockedConstraint
62+
->expects($invocationRules['toString'] ?? $this->any())
63+
->method('toString')
64+
->willReturnCallback([ $constraint, 'toString' ]);
65+
66+
if (isset($invocationRules['evaluate']) && ($evaluateParameters !== null)) {
67+
$mockedConstraint
68+
->expects($invocationRules['evaluate'])
69+
->method('evaluate')
70+
->with(...$evaluateParameters)
71+
->willReturnCallback([ $constraint, 'evaluate' ]);
72+
} else {
73+
$mockedConstraint
74+
->expects($invocationRules['evaluate'] ?? $this->never())
75+
->method('evaluate')
76+
->willReturnCallback([ $constraint, 'evaluate' ]);
77+
}
78+
79+
$mockedConstraint
80+
->expects($invocationRules['count'] ?? $this->any())
81+
->method('count')
82+
->willReturnCallback([ $constraint, 'count' ]);
83+
84+
return $mockedConstraint;
85+
}
86+
87+
/**
88+
* @param string $testName
89+
*
90+
* @return array[]
91+
*/
92+
protected function getTestDataSets(string $testName): array
93+
{
94+
$error = null;
95+
$testClassName = (new ReflectionObject($this))->getShortName();
96+
$testDatasetsFile = __DIR__ . '/data/' . $testClassName . '.yml';
97+
98+
if (!isset(self::$testDataSets[$testClassName])) {
99+
if (!file_exists($testDatasetsFile)) {
100+
$error = 'No such file or directory';
101+
} elseif (!is_file($testDatasetsFile)) {
102+
$error = 'Not a file';
103+
} elseif (!is_readable($testDatasetsFile)) {
104+
$error = 'Permission denied';
105+
} else {
106+
try {
107+
self::$testDataSets[$testClassName] = $this->parseYaml(file_get_contents($testDatasetsFile));
108+
} catch (YamlParseException $e) {
109+
$error = sprintf('YAML parse error: %s', $e->getMessage());
110+
}
111+
}
112+
}
113+
114+
if (!isset(self::$testDataSets[$testClassName][$testName])) {
115+
if ($error === null) {
116+
$error = sprintf('Dataset "%s" not found', $testName);
117+
}
118+
}
119+
120+
if ($error !== null) {
121+
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
122+
123+
throw new PHPUnitException(sprintf(
124+
'Test data file "%s" for %s::%s() (%s data sets) is invalid: %s',
125+
$testDatasetsFile,
126+
$stack[1]['class'],
127+
$stack[1]['function'],
128+
$testClassName,
129+
$error
130+
));
131+
}
132+
133+
return self::$testDataSets[$testClassName][$testName];
134+
}
135+
136+
/**
137+
* @param string $input
138+
*
139+
* @return mixed
140+
*
141+
* @throws YamlParseException
142+
*/
143+
private function parseYaml(string $input)
144+
{
145+
$yaml = Yaml::parse($input);
146+
147+
if (isset($yaml['~anchors'])) {
148+
unset($yaml['~anchors']);
149+
}
150+
151+
$parseRecursive = static function ($value) use (&$parseRecursive) {
152+
if (is_array($value)) {
153+
if (isset($value['<<<'])) {
154+
$mergeValues = $value['<<<'];
155+
unset($value['<<<']);
156+
157+
if (is_array($mergeValues) && $mergeValues) {
158+
if (!isset($mergeValues[0]) || ($mergeValues !== array_values($mergeValues))) {
159+
$mergeValues = [ $mergeValues ];
160+
}
161+
162+
foreach ($mergeValues as $mergeValue) {
163+
$value = array_replace_recursive($value, $mergeValue);
164+
}
165+
}
166+
}
167+
168+
if (isset($value['~generator'])) {
169+
$options = $value['~generator'];
170+
$generator = static function (int $start, int $step, int $stop): Generator {
171+
for ($i = $start; $i < $stop; $i += $step) {
172+
yield $i;
173+
}
174+
};
175+
176+
return $generator($options['start'] ?? 10, $options['step'] ?? 1, $options['stop'] ?? 10);
177+
}
178+
179+
if (isset($value['~object'])) {
180+
$className = $value['~object'];
181+
unset($value['~object']);
182+
183+
$parameters = array_values(array_map($parseRecursive, $value));
184+
return new $className(...$parameters);
185+
}
186+
187+
return array_map($parseRecursive, $value);
188+
}
189+
190+
return $value;
191+
};
192+
193+
return $parseRecursive($yaml);
194+
}
195+
}

0 commit comments

Comments
 (0)