Skip to main content

Write Tests

Groups define the subject for the test, test begins with an input and a set of expectations and each validation describes one of its responsibilities with clear intent.


Writing Tests in Unitary

A unitary test file can include one or more group(), each serving as a container for tests that validate a specific subject. Each group runs within its own isolated scope, allowing you to safely add business logic while keeping side effects and dependencies contained. You can then add multiple tests within each group.

use MaplePHP\Unitary\{Expect,TestCase};

group("HTTP Request", function(TestCase $case) {

$request = new Request("GET", "https://example.com/?id=1&slug=hello");

$case->expect(1 + 1)
->isEqualTo(2)
->validate();

$case->expect($request->getMethod())
->isRequestMethod()
->validate();

$case->expect($request->getUri()->getQuery())
->hasQueryParam("id", 1)
->hasQueryParam("slug", "hello")
->validate();
});

Execute

php vendor/bin/unitary

Response

Unitary CLI response


Descriptions

Descriptions are optional and useful when the purpose of a validation isn’t immediately obvious.

use MaplePHP\Unitary\{Expect,TestCase};

group("Validating API Response", function(TestCase $case) {

$json = '{"response":{"status":404,"message":"ok"}}';

$case->expect($json)
->isJson()
->describe("Response must be valid JSON")
->hasJsonValueAt("response.status", 200)
->describe("Response status must be 200")
->validate("API response status");
});
  • describe(): Add optional description to explain the validation and chain them when more clarity is needed.
  • validate(): You can define a optional heading for the validation.

Response

Unitary CLI response


Hard stops

A hard stop aborts the current group immediately. No further validations or logic inside that group will run but will continue with the next group in the same test file if it exists.

You may chain assert() as the final step, replaces validate() to trigger a hard stop in the group. Validations below it will not run, which is helpful when later checks depend on earlier conditions or when you want to avoid cascading errors.

use MaplePHP\Unitary\{Expect,TestCase};

group("Validating API Response", function(TestCase $case) {

$json = '{"response":{"status":404,"message":"ok"}}';

$case->expect($json)
->isJson()
->hasJsonValueAt("response.status", 200)
->assert("API response status");
});

Response

Unitary CLI response


Debug output

Unitary is built to make testing feel natural. That means you can debug with normal print_r(), var_dump(), or echo and Unitary will capture and display the output neatly in the CLI.

use MaplePHP\Unitary\{Expect,TestCase};

group("Debug example", function(TestCase $case) {
print_r(['milk', 'cheese', 'bread']);
});

Note: Prints made outside of a group() may not appear in the CLI stream. If you need to see such output, you need to call die() or exit() after printing.

Response

Unitary CLI response


Advanced usage

Callback-based validation

Use the callback-based validation when a validation needs staged or conditional logic.

use MaplePHP\Unitary\{TestCase,Expect};

group("Advanced example", function(TestCase $case) {

$json = '{"response":{"status":200,"message":"ok"}}';

$case->validate($json, function(Expect $expect) {

$expect->isJson()
->hasJsonValueAt("response.status", 200);
});

});

Soft stop

The callback-based validation also supports soft stops, which end only the current validation block. This is ideal for dependent checks or logic that should run only after earlier conditions succeed.

use MaplePHP\Unitary\{TestCase,Expect};

group("2. Soft stop examples", function(TestCase $case) {
$case->validate($userData, function(Expect $expect) {

$expect->isArray()->assert("User data must be an array");

$data = $expect->val();

if (($data['role'] ?? null) === 'admin') {
$expect->hasArrayKey('permissions')
->assert("Admin missing permissions");
}

$expect->hasArrayKey('email');
});

});

Use this when the validation itself needs internal structure.


Quick custom validations

When a test needs logic that isn’t covered by Unitary’s built-in expectations, you can define custom checks directly. A native assert() inside a group creates a hard stop, while an assert() inside a validation callback becomes a soft stop and the validation is still counted.

use MaplePHP\Unitary\{Expect,TestCase};

group("Preconditions", function(TestCase $case) {

// Hard stop: group ends immediately if this fails
assert(1 === 1, "Setup is invalid 1");

// Soft stop inside a validation: validation is counted
$case->validate($userData, function(Expect $expect) {
assert(1 === 1, "Setup is invalid 2");
});

// Custom validation returning a boolean
$case->validate(1, function(Expect $expect) {
return $expect->val() === 1;
});
});

You now know how to create and structure Unitary tests from simple validations to advanced grouped assertions. For a complete list of validation methods that make your testing even easier, see the Validation API.