Testing
Application is a PSR-15 request handler. Tests can send a PSR-7 request
directly to the application without starting RoadRunner or opening a port.
use Nyholm\Psr7\ServerRequest;
$app = new Application($configuration);
$response = $app->handle(new ServerRequest('GET', '/dogs'));
expect($response->getStatusCode())->toBe(200);
expect((string) $response->getBody())->toBe('{"dogs":[]}');
Set query, parsed-body, cookie, and request attributes with the regular PSR-7
methods before passing the request to handle():
$request = (new ServerRequest('POST', '/dogs'))
->withQueryParams(['notify' => 'true'])
->withParsedBody(['name' => 'Gustav'])
->withCookieParams(['session' => 'example']);
$response = $app->handle($request);
For session tests, reuse the Set-Cookie ID through withCookieParams() on a
later request. Point SessionOptions at an isolated temporary directory, or
register an in-memory SessionStoreInterface as a discovered singleton. This
exercises the same request-scoped session and CSRF middleware as RoadRunner.
RoadRunner remains the production transport used by Application::run(); the
request behavior is shared by both entry points.
Configuration overrides
Create an isolated environment map when a test needs typed application
configuration. This avoids putenv() and prevents process-global state from
leaking between tests:
use GustavPHP\Gustav\Config\Environment;
$configuration = Configuration::forProject(
namespace: 'App',
root: dirname(__DIR__),
environment: Environment::fromArray([
'DATABASE_URL' => 'sqlite::memory:',
'FEATURE_ENABLED' => 'false',
]),
);
$app = new Application($configuration);
The normal startup path still runs, so missing variables, conversions,
defaults, backed enums, and #[Validate] rules behave exactly as they do in a
worker.
Testing commands
Application commands run in process through Symfony's ApplicationTester:
use Symfony\Component\Console\Tester\ApplicationTester;
$configuration = require dirname(__DIR__) . '/app/bootstrap.php';
$console = (new Application($configuration))->console();
$tester = new ApplicationTester($console);
$status = $tester->run([
'command' => 'users:sync',
'tenant' => 'acme',
'--dry-run' => true,
]);
expect($status)->toBe(0);
The tester uses normal command discovery, typed input conversion, validation, dependency injection, exception rendering, and scope cleanup without opening a port. See Commands for the full example and exit-code contract.