Issues (7)

src/TAssertions.php (1 issue)

Severity
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
Silencing errors is discouraged; found: @file_get_contents($filename...
Loading history...
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