PHP unit tests with closures

A unit test checks one behaviour of a small piece of code. PHPUnit and Pest are the standard frameworks, but a test is simply a function that calls the code and compares the result. Storing the tests in an array of name => closure lets a loop run them all and report each result, so a failing test does not stop the others.

PHP
<?php
class AssertionFailure extends Exception {}

function assertSame(mixed $expected, mixed $actual): void
{
    if ($expected !== $actual) {
        throw new AssertionFailure("expected " . var_export($expected, true) . " but got " . var_export($actual, true));
    }
}

function assertThrows(string $class, callable $code): void
{
    try {
        $code();
    } catch (Throwable $e) {
        if ($e instanceof $class) {
            return;
        }
        throw new AssertionFailure("expected $class but got " . get_class($e));
    }
    throw new AssertionFailure("expected $class but nothing was thrown");
}

function clamp(int $value, int $low, int $high): int
{
    if ($low > $high) {
        throw new InvalidArgumentException("low > high");
    }
    return max($low, min($high, $value));
}

$tests = [
    "inside the range" => fn() => assertSame(5, clamp(5, 0, 10)),
    "below the range" => fn() => assertSame(0, clamp(-3, 0, 10)),
    "above the range" => fn() => assertSame(10, clamp(99, 0, 10)),
    "bad bounds throw" => fn() => assertThrows(InvalidArgumentException::class, fn() => clamp(1, 5, 2)),
    "division by zero" => fn() => assertThrows(DivisionByZeroError::class, fn() => intdiv(1, 0)),
    "a failing check" => fn() => assertSame(6, clamp(7, 0, 5)),
];

$failed = 0;
foreach ($tests as $name => $test) {
    try {
        $test();
        echo "PASS $name\n";
    } catch (AssertionFailure $e) {
        $failed++;
        echo "FAIL $name: ", $e->getMessage(), "\n";
    }
}
echo count($tests) - $failed, " passed, $failed failed\n";
Read the full lesson