1 | <?php |
||
2 | declare(strict_types=1); |
||
3 | |||
4 | namespace MyTester; |
||
5 | |||
6 | use ArrayAccess; |
||
7 | use Countable; |
||
8 | |||
9 | /** |
||
10 | * Default assertions and helper methods for {@see TestCase} |
||
11 | * |
||
12 | * @author Jakub Konečný |
||
13 | */ |
||
14 | trait TAssertions |
||
15 | { |
||
16 | private const array PRIMITIVE_TYPES = [ |
||
17 | "array", "bool", "float", "int", "string", "null", "object", "resource", "scalar", "iterable", "callable", |
||
18 | ]; |
||
19 | |||
20 | private int $taskCount = 0; |
||
21 | |||
22 | /** |
||
23 | * Prints result of a test |
||
24 | */ |
||
25 | final protected function testResult(string $text, bool $success = true): void |
||
26 | { |
||
27 | 1 | $this->incCounter(); |
|
28 | 1 | if ($success) { |
|
29 | 1 | return; |
|
30 | } |
||
31 | 1 | throw new AssertionFailedException($text, $this->getCounter()); |
|
32 | } |
||
33 | |||
34 | /** |
||
35 | * Increases task counter |
||
36 | */ |
||
37 | final protected function incCounter(): void |
||
38 | { |
||
39 | 1 | $this->taskCount++; |
|
40 | 1 | } |
|
41 | |||
42 | /** |
||
43 | * Resets task counter |
||
44 | */ |
||
45 | final protected function resetCounter(): void |
||
46 | { |
||
47 | 1 | $this->taskCount = 0; |
|
48 | 1 | } |
|
49 | |||
50 | /** |
||
51 | * @internal |
||
52 | */ |
||
53 | final public function getCounter(): int |
||
54 | { |
||
55 | 1 | return $this->taskCount; |
|
56 | } |
||
57 | |||
58 | protected function showValue(mixed $variable): string |
||
59 | { |
||
60 | 1 | if (is_string($variable)) { |
|
61 | 1 | return "'$variable'"; |
|
62 | } |
||
63 | 1 | return var_export($variable, true); |
|
64 | } |
||
65 | |||
66 | /** |
||
67 | * Tries an assertion |
||
68 | */ |
||
69 | protected function assert(mixed $code, string $failureText = ""): void |
||
70 | { |
||
71 | 1 | $success = ((bool) $code === true); |
|
72 | 1 | $message = ""; |
|
73 | 1 | if (!$success) { |
|
74 | 1 | $message = ($failureText === "") ? "The assertion is not true." : $failureText; |
|
75 | } |
||
76 | 1 | $this->testResult($message, $success); |
|
77 | 1 | } |
|
78 | |||
79 | /** |
||
80 | * Are both values same? |
||
81 | */ |
||
82 | protected function assertSame(mixed $expected, mixed $actual): void |
||
83 | { |
||
84 | 1 | $success = ($expected === $actual); |
|
85 | 1 | $message = ""; |
|
86 | 1 | if (!$success) { |
|
87 | 1 | $message = sprintf("The value is not %s but %s.", $this->showValue($expected), $this->showValue($actual)); |
|
88 | } |
||
89 | 1 | $this->testResult($message, $success); |
|
90 | 1 | } |
|
91 | |||
92 | /** |
||
93 | * Are not both values same? |
||
94 | */ |
||
95 | protected function assertNotSame(mixed $expected, mixed $actual): void |
||
96 | { |
||
97 | 1 | $success = ($expected !== $actual); |
|
98 | 1 | $message = ""; |
|
99 | 1 | if (!$success) { |
|
100 | 1 | $message = sprintf("The value is %s.", $this->showValue($expected)); |
|
101 | } |
||
102 | 1 | $this->testResult($message, $success); |
|
103 | 1 | } |
|
104 | |||
105 | /** |
||
106 | * Is $actual greater than $expected? |
||
107 | */ |
||
108 | protected function assertGreaterThan(int|float $expected, int|float $actual): void |
||
109 | { |
||
110 | 1 | $success = ($actual > $expected); |
|
111 | 1 | $message = ($success) ? "" : "$actual is not greater than $expected."; |
|
112 | 1 | $this->testResult($message, $success); |
|
113 | 1 | } |
|
114 | |||
115 | /** |
||
116 | * Is $actual less than $expected? |
||
117 | */ |
||
118 | protected function assertLessThan(int|float $expected, int|float $actual): void |
||
119 | { |
||
120 | 1 | $success = ($actual < $expected); |
|
121 | 1 | $message = ($success) ? "" : "$actual is not less than $expected."; |
|
122 | 1 | $this->testResult($message, $success); |
|
123 | 1 | } |
|
124 | |||
125 | /** |
||
126 | * Is $actual equal to true? |
||
127 | */ |
||
128 | protected function assertTrue(bool $actual): void |
||
129 | { |
||
130 | 1 | $success = ($actual); |
|
131 | 1 | $message = ($success) ? "" : "The value is not true."; |
|
132 | 1 | $this->testResult($message, $success); |
|
133 | 1 | } |
|
134 | |||
135 | /** |
||
136 | * Is the expression true? |
||
137 | */ |
||
138 | protected function assertTruthy(mixed $actual): void |
||
139 | { |
||
140 | 1 | $success = ((bool) $actual === true); |
|
141 | 1 | $message = ($success) ? "" : "The expression is not true."; |
|
142 | 1 | $this->testResult($message, $success); |
|
143 | 1 | } |
|
144 | |||
145 | /** |
||
146 | * Is $actual equal to false? |
||
147 | */ |
||
148 | protected function assertFalse(bool $actual): void |
||
149 | { |
||
150 | 1 | $success = (!$actual); |
|
151 | 1 | $message = ($success) ? "" : "The value is not false."; |
|
152 | 1 | $this->testResult($message, $success); |
|
153 | 1 | } |
|
154 | |||
155 | /** |
||
156 | * Is the expression false? |
||
157 | */ |
||
158 | protected function assertFalsey(mixed $actual): void |
||
159 | { |
||
160 | 1 | $success = ((bool) $actual === false); |
|
161 | 1 | $message = ($success) ? "" : "The expression is not false."; |
|
162 | 1 | $this->testResult($message, $success); |
|
163 | 1 | } |
|
164 | |||
165 | /** |
||
166 | * Is the value null? |
||
167 | */ |
||
168 | protected function assertNull(mixed $actual): void |
||
169 | { |
||
170 | 1 | $success = ($actual === null); |
|
171 | 1 | $message = ($success) ? "" : "The value is not null."; |
|
172 | 1 | $this->testResult($message, $success); |
|
173 | 1 | } |
|
174 | |||
175 | /** |
||
176 | * Is not the value null? |
||
177 | */ |
||
178 | protected function assertNotNull(mixed $actual): void |
||
179 | { |
||
180 | 1 | $success = ($actual !== null); |
|
181 | 1 | $message = ($success) ? "" : "The value is null."; |
|
182 | 1 | $this->testResult($message, $success); |
|
183 | 1 | } |
|
184 | |||
185 | /** |
||
186 | * Does $actual contain $needle? |
||
187 | * |
||
188 | * @param string|mixed[] $needle |
||
189 | * @param string|mixed[] $actual |
||
190 | */ |
||
191 | protected function assertContains(string|array $needle, string|array $actual): void |
||
192 | { |
||
193 | 1 | if (is_string($actual) && is_string($needle)) { |
|
194 | 1 | $success = ($needle !== "" && str_contains($actual, $needle)); |
|
195 | 1 | $message = ($success) ? "" : "$needle is not in the variable."; |
|
196 | 1 | $this->testResult($message, $success); |
|
197 | 1 | } elseif (is_array($actual)) { |
|
198 | 1 | $success = (in_array($needle, $actual, true)); |
|
199 | 1 | $message = ($success) ? "" : $this->showValue($needle) . " is not in the variable."; |
|
200 | 1 | $this->testResult($message, $success); |
|
201 | } else { |
||
202 | 1 | $this->testResult($this->showValue($needle) . " is not in the variable.", false); |
|
203 | } |
||
204 | 1 | } |
|
205 | |||
206 | /** |
||
207 | * Does $actual not contain $needle? |
||
208 | * |
||
209 | * @param string|mixed[] $needle |
||
210 | * @param string|mixed[] $actual |
||
211 | */ |
||
212 | protected function assertNotContains(string|array $needle, string|array $actual): void |
||
213 | { |
||
214 | 1 | if (is_string($actual) && is_string($needle)) { |
|
215 | 1 | $success = ($needle === "" || !str_contains($actual, $needle)); |
|
216 | 1 | $message = ($success) ? "" : "$needle is in the variable."; |
|
217 | 1 | $this->testResult($message, $success); |
|
218 | 1 | } elseif (is_array($actual)) { |
|
219 | 1 | $success = (!in_array($needle, $actual, true)); |
|
220 | 1 | $message = ($success) ? "" : $this->showValue($needle) . " is in the variable."; |
|
221 | 1 | $this->testResult($message, $success); |
|
222 | } else { |
||
223 | 1 | $this->testResult($this->showValue($needle) . " is not in the variable.", false); |
|
224 | } |
||
225 | 1 | } |
|
226 | |||
227 | /** |
||
228 | * Does $value contain $count items? |
||
229 | * |
||
230 | * @param mixed[]|Countable $value |
||
231 | */ |
||
232 | protected function assertCount(int $count, array|Countable $value): void |
||
233 | { |
||
234 | 1 | $actual = count($value); |
|
235 | 1 | $success = ($actual === $count); |
|
236 | 1 | $message = ($success) ? "" : "Count of the variable is not $count but $actual."; |
|
237 | 1 | $this->testResult($message, $success); |
|
238 | 1 | } |
|
239 | |||
240 | /** |
||
241 | * Does $value not contain $count items? |
||
242 | * |
||
243 | * @param mixed[]|Countable $value |
||
244 | */ |
||
245 | protected function assertNotCount(int $count, array|Countable $value): void |
||
246 | { |
||
247 | 1 | $actual = count($value); |
|
248 | 1 | $success = ($actual !== $count); |
|
249 | 1 | $message = ($success) ? "" : "Count of the variable is $actual."; |
|
250 | 1 | $this->testResult($message, $success); |
|
251 | 1 | } |
|
252 | |||
253 | /** |
||
254 | * Is $value of type $type? |
||
255 | */ |
||
256 | protected function assertType(string|object $expected, mixed $value): void |
||
257 | { |
||
258 | 1 | if (in_array($expected, self::PRIMITIVE_TYPES, true)) { |
|
259 | 1 | $success = (call_user_func("is_$expected", $value)); |
|
260 | 1 | $actual = gettype($value); |
|
261 | 1 | $message = ($success) ? "" : "The variable is of type $actual not $expected."; |
|
262 | 1 | $this->testResult($message, $success); |
|
263 | 1 | return; |
|
264 | } |
||
265 | 1 | $success = ($value instanceof $expected); |
|
266 | 1 | $actual = get_debug_type($value); |
|
267 | 1 | $message = ($success) ? |
|
268 | 1 | "" : |
|
269 | 1 | "The variable is instance of $actual not " . (is_string($expected) ? $expected : $expected::class) . "."; |
|
270 | 1 | $this->testResult($message, $success); |
|
271 | 1 | } |
|
272 | |||
273 | /** |
||
274 | * Does the code throw the expected exception? |
||
275 | * |
||
276 | * @param class-string $className |
||
277 | */ |
||
278 | protected function assertThrowsException( |
||
279 | callable $callback, |
||
280 | string $className, |
||
281 | ?string $message = null, |
||
282 | ?int $code = null |
||
283 | ): void { |
||
284 | 1 | $success = false; |
|
285 | 1 | $errorMessage = ""; |
|
286 | 1 | $e = null; |
|
287 | try { |
||
288 | 1 | $callback(); |
|
289 | 1 | } catch (\Throwable $e) { |
|
290 | 1 | if ($e instanceof $className) { |
|
291 | if ( |
||
292 | 1 | ($message === null || $e->getMessage() === $message) && ($code === null || $e->getCode() === $code) |
|
293 | ) { |
||
294 | 1 | $success = true; |
|
295 | } |
||
296 | } |
||
297 | } |
||
298 | 1 | if (!$success) { |
|
299 | 1 | if ($e === null) { |
|
300 | 1 | $errorMessage = "The code does not throw any exception."; |
|
301 | 1 | } elseif (!$e instanceof $className) { |
|
302 | 1 | $errorMessage = "The code does not throw $className but " . get_class($e) . "."; |
|
303 | 1 | } elseif ($message !== null && $message !== $e->getMessage()) { |
|
304 | 1 | $errorMessage = |
|
305 | 1 | "The code does not throw an exception with message '$message' but '{$e->getMessage()}'."; |
|
306 | 1 | } elseif ($code !== null && $code !== $e->getCode()) { |
|
307 | 1 | $errorMessage = "The code does not throw an exception with code $code but {$e->getCode()}."; |
|
308 | } |
||
309 | } |
||
310 | 1 | $this->testResult($errorMessage, $success); |
|
311 | 1 | } |
|
312 | |||
313 | protected function assertNoException(callable $callback): void |
||
314 | { |
||
315 | 1 | $e = null; |
|
316 | try { |
||
317 | 1 | $callback(); |
|
318 | 1 | } catch (\Throwable $e) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement |
|
319 | } |
||
320 | 1 | $success = ($e === null); |
|
321 | 1 | $message = ($success) ? "" : "No exception was expected but " . $e::class . " was thrown."; |
|
322 | 1 | $this->testResult($message, $success); |
|
323 | 1 | } |
|
324 | |||
325 | /** |
||
326 | * Is output of code $expected? |
||
327 | */ |
||
328 | protected function assertOutput(callable $callback, string $expected): void |
||
329 | { |
||
330 | 1 | ob_start(); |
|
331 | 1 | $callback(); |
|
332 | 1 | $output = (string) ob_get_clean(); |
|
333 | 1 | $success = ($expected === $output); |
|
334 | 1 | $message = ($success) ? "" : "Output of code is not '$expected' but '$output'."; |
|
335 | 1 | $this->testResult($message, $success); |
|
336 | 1 | } |
|
337 | |||
338 | /** |
||
339 | * Does $actual matches regular expression $expected? |
||
340 | */ |
||
341 | protected function assertMatchesRegExp(string $expected, string $actual): void |
||
342 | { |
||
343 | 1 | $success = (preg_match($expected, $actual) === 1); |
|
344 | 1 | $message = ($success) ? "" : "The string does not match regular expression."; |
|
345 | 1 | $this->testResult($message, $success); |
|
346 | 1 | } |
|
347 | |||
348 | /** |
||
349 | * Does $actual matches content of file $filename? |
||
350 | */ |
||
351 | protected function assertMatchesFile(string $filename, string $actual): void |
||
352 | { |
||
353 | 1 | $expected = @file_get_contents($filename); |
|
0 ignored issues
–
show
introduced
by
![]() |
|||
354 | 1 | if ($expected === false) { |
|
355 | 1 | $this->testResult("File $filename could not be loaded.", false); |
|
356 | } |
||
357 | 1 | $this->assertSame($expected, $actual); |
|
358 | 1 | } |
|
359 | |||
360 | /** |
||
361 | * Is $actual an array consisting only of values of type $actual |
||
362 | * |
||
363 | * @param mixed[] $actual |
||
364 | */ |
||
365 | protected function assertArrayOfType(string|object $expected, array $actual): void |
||
366 | { |
||
367 | 1 | if (count($actual) === 0) { |
|
368 | 1 | $this->testResult("The array is empty.", false); |
|
369 | } |
||
370 | 1 | $success = array_all($actual, function (mixed $value) use ($expected) { |
|
371 | 1 | if (in_array($expected, self::PRIMITIVE_TYPES, true)) { |
|
372 | 1 | return (call_user_func("is_$expected", $value)); |
|
373 | } |
||
374 | 1 | return ($value instanceof $expected); |
|
375 | 1 | }); |
|
376 | 1 | $type = get_debug_type($expected); |
|
377 | 1 | $message = ($success) ? "" : "The array does not contain only values of type $type."; |
|
378 | 1 | $this->testResult($message, $success); |
|
379 | 1 | } |
|
380 | |||
381 | protected function assertTriggersDeprecation(callable $callback, string $expected = ""): void |
||
382 | { |
||
383 | 1 | $deprecation = ""; |
|
384 | 1 | set_error_handler( |
|
385 | 1 | function (int $errno, string $errstr, string $errfile, int $errline) use (&$deprecation) { |
|
386 | 1 | $deprecation = $errstr; |
|
387 | 1 | return true; |
|
388 | 1 | }, |
|
389 | 1 | E_USER_DEPRECATED |
|
390 | ); |
||
391 | 1 | $callback(); |
|
392 | 1 | restore_error_handler(); |
|
393 | 1 | if ($deprecation === "") { |
|
394 | 1 | $success = false; |
|
395 | 1 | $message = "Expected a deprecation but none was triggered."; |
|
396 | } else { |
||
397 | 1 | $success = ($expected === "" || $deprecation === $expected); |
|
398 | 1 | $message = ($success) ? "" : "Expected deprecation '$expected' but '$deprecation' was triggered."; |
|
399 | } |
||
400 | 1 | $this->testResult($message, $success); |
|
401 | 1 | } |
|
402 | |||
403 | protected function assertTriggersNoDeprecation(callable $callback): void |
||
404 | { |
||
405 | 1 | $deprecation = ""; |
|
406 | 1 | set_error_handler( |
|
407 | 1 | function (int $errno, string $errstr, string $errfile, int $errline) use (&$deprecation) { |
|
408 | 1 | $deprecation = $errstr; |
|
409 | 1 | return true; |
|
410 | 1 | }, |
|
411 | 1 | E_USER_DEPRECATED |
|
412 | ); |
||
413 | 1 | $callback(); |
|
414 | 1 | restore_error_handler(); |
|
415 | 1 | $success = ($deprecation === ""); |
|
416 | 1 | $message = ($success) ? |
|
417 | 1 | "" : |
|
418 | 1 | "Expected no deprecation but " . $this->showValue($deprecation) . " was triggered."; |
|
419 | 1 | $this->testResult($message, $success); |
|
420 | 1 | } |
|
421 | |||
422 | /** |
||
423 | * @param mixed[]|ArrayAccess<mixed, mixed> $array |
||
424 | */ |
||
425 | protected function assertArrayHasKey(string|int $key, array|ArrayAccess $array): void |
||
426 | { |
||
427 | 1 | $success = ($array instanceof ArrayAccess ? $array->offsetExists($key) : array_key_exists($key, $array)); |
|
428 | 1 | $message = ($success) ? "" : "The array does not contain key " . $this->showValue($key) . "."; |
|
429 | 1 | $this->testResult($message, $success); |
|
430 | 1 | } |
|
431 | |||
432 | /** |
||
433 | * @param mixed[]|ArrayAccess<mixed, mixed> $array |
||
434 | */ |
||
435 | protected function assertArrayNotHasKey(string|int $key, array|ArrayAccess $array): void |
||
436 | { |
||
437 | 1 | $success = ($array instanceof ArrayAccess ? !$array->offsetExists($key) : !array_key_exists($key, $array)); |
|
438 | 1 | $message = ($success) ? "" : "The array contains key " . $this->showValue($key) . "."; |
|
439 | 1 | $this->testResult($message, $success); |
|
440 | 1 | } |
|
441 | |||
442 | /** |
||
443 | * @param Countable|mixed[] $expected |
||
444 | * @param Countable|mixed[] $actual |
||
445 | */ |
||
446 | protected function assertSameSize(Countable|array $expected, Countable|array $actual): void |
||
447 | { |
||
448 | 1 | $success = (count($expected) === count($actual)); |
|
449 | 1 | $message = ($success) ? "" : sprintf("Actual count is %d not %d.", count($actual), count($expected)); |
|
450 | 1 | $this->testResult($message, $success); |
|
451 | 1 | } |
|
452 | |||
453 | protected function assertFileExists(string $fileName): void |
||
454 | { |
||
455 | 1 | $success = (is_file($fileName)); |
|
456 | 1 | $message = ($success) ? "" : "File $fileName does not exist."; |
|
457 | 1 | $this->testResult($message, $success); |
|
458 | 1 | } |
|
459 | |||
460 | protected function assertFileNotExists(string $fileName): void |
||
461 | { |
||
462 | 1 | $success = (!is_file($fileName)); |
|
463 | 1 | $message = ($success) ? "" : "File $fileName exists."; |
|
464 | 1 | $this->testResult($message, $success); |
|
465 | 1 | } |
|
466 | |||
467 | protected function assertDirectoryExists(string $directoryName): void |
||
468 | { |
||
469 | 1 | $success = (is_dir($directoryName)); |
|
470 | 1 | $message = ($success) ? "" : "Directory $directoryName does not exist."; |
|
471 | 1 | $this->testResult($message, $success); |
|
472 | 1 | } |
|
473 | |||
474 | protected function assertDirectoryNotExists(string $directoryName): void |
||
475 | { |
||
476 | 1 | $success = (!is_dir($directoryName)); |
|
477 | 1 | $message = ($success) ? "" : "Directory $directoryName exists."; |
|
478 | 1 | $this->testResult($message, $success); |
|
479 | 1 | } |
|
480 | } |
||
481 |