Testing

Preflow provides PHPUnit base cases for testing components, routes, and data layers. The testing package handles bootstrap, provides fluent assertions, and manages isolated storage for each test.

Installation

composer require preflow/testing --dev

ComponentTestCase

Extend ComponentTestCase to test Preflow components. A TestApplication is wired up automatically in setUp().

use Preflow\Testing\ComponentTestCase;

final class CounterTest extends ComponentTestCase
{
    public function test_renders_count(): void
    {
        $html = $this->renderComponent(Counter::class, [
            'initialCount' => 5,
        ]);

        $this->assertStringContainsString('5', $html);
    }

    public function test_initial_state(): void
    {
        $counter = $this->createComponent(Counter::class, ['initialCount' => 0]);
        $counter->resolveState();

        $this->assertSame(0, $counter->count);
    }
}
Method Description
renderComponent(class, props) Full render lifecycle, returns HTML string
createComponent(class, props) Creates component instance without rendering

RouteTestCase and TestResponse

RouteTestCase provides an application instance. Wrap any PSR-7 response in TestResponse for fluent assertions:

use Preflow\Testing\RouteTestCase;

final class HealthTest extends RouteTestCase
{
    public function test_health_endpoint(): void
    {
        $response = $this->app->handle($this->createRequest('GET', '/api/health'));

        $this->createTestResponse($response)
            ->assertOk()
            ->assertJson()
            ->assertSee('"status":"ok"');
    }
}

TestResponse Assertions

Method Description
assertOk() Status 200
assertStatus(int) Exact status code
assertSee(string) Body contains text
assertNotSee(string) Body does not contain text
assertRedirect(string) 3xx status + matching Location header
assertHeader(name, value) Exact header value
assertHeaderContains(name, sub) Header contains substring
assertJson() Content-Type contains "json" + valid JSON body
assertForbidden() Status 403
assertNotFound() Status 404
assertUnauthorized() Status 401

All assertion methods return $this for chaining.

DataTestCase

DataTestCase provides isolated storage for each test: an in-memory SQLite connection and a temporary JSON directory, both torn down after each test.

use Preflow\Testing\DataTestCase;
use Preflow\Data\Migration\Table;

final class PostRepositoryTest extends DataTestCase
{
    protected function setUp(): void
    {
        parent::setUp();

        $this->createTable('posts', function (Table $table) {
            $table->uuid('uuid')->primary();
            $table->string('title');
            $table->timestamps();
        });
    }

    public function test_save_and_find(): void
    {
        $pdo = $this->getPdo();
        $pdo->exec("INSERT INTO posts (uuid, title) VALUES ('1', 'Hello')");

        $row = $pdo->query("SELECT * FROM posts WHERE uuid = '1'")->fetch();
        $this->assertSame('Hello', $row['title']);
    }
}

DataTestCase Methods

Method Returns
getSqliteDriver() SqliteDriver (in-memory SQLite)
getJsonDriver() JsonFileDriver (temp directory)
dataManager() DataManager with both drivers registered
createTable(name, callback) Creates a table via the Schema builder
getPdo() Raw \PDO instance

Auth Test Helpers

Test authenticated requests with the AuthTestHelpers trait:

use Preflow\Testing\AuthTestHelpers;

final class DashboardTest extends RouteTestCase
{
    use AuthTestHelpers;

    public function test_dashboard_requires_auth(): void
    {
        $user = new TestUser(uuid: 'u1', roles: ['admin']);
        $request = $this->actingAs($user, $this->createRequest('GET', '/dashboard'));

        $response = $this->app->handle($request);
        $this->createTestResponse($response)->assertOk();
    }
}

Running Tests

./vendor/bin/phpunit

Tests live in the tests/ directory. The skeleton includes example tests for components and routing to get you started.