Skip to content

Testing

Keel\Testing\TestClient sends requests through the kernel without a SAPI or sockets, and TestResponse offers chained PHPUnit assertions. Kernel::terminate() runs after every request, exactly like in a worker.

final class UsersTest extends TestCase
{
    private TestClient $client;

    protected function setUp(): void
    {
        $app = App::boot(dirname(__DIR__), new Env(['APP_ENV' => 'test', 'APP_DEBUG' => '1']));
        $this->client = TestClient::forApp($app);
    }

    public function testCreate(): void
    {
        $this->client->json('POST', '/api/users', ['name' => 'Ann', 'email' => 'a@b.c'])
            ->assertStatus(201)
            ->assertJson(['name' => 'Ann'])
            ->assertJsonPath('address.city', null);

        $this->client->json('POST', '/api/users', ['name' => ''])
            ->assertProblem(400, 'Invalid request data', ['name: must not be blank', 'email: field is required']);
    }
}
Client method What it does
get, delete request without a body
post, put, patch array → application/x-www-form-urlencoded and getParsedBody(), string → raw body
json($method, $uri, $data) JSON body with Content-Type and Accept headers
request($method, $uri, $headers, $body) the general case
withHeader, withCookie, withServer defaults for the following requests, return a new client

TestResponse assertions: assertStatus, assertOk, assertHeader, assertHeaderMissing, assertBodyContains, assertJson (subset, recursive), assertJsonPath('a.b', value), assertJsonMissing, assertProblem($status, $detail, $errors). Data access: status(), header(), body(), json(), jsonPath(), response.

In debug mode the kernel caches nothing but discovery, so APP_DEBUG=1 in tests avoids stale routes.php and container.php. The debug container runs on reflection but behaves exactly like the compiled one: both pass the same test suite.