NAME
PAGI::Test::Client - Test client for PAGI applications
SYNOPSIS
use PAGI::Test::Client;
my $client = PAGI::Test::Client->new(app => $app);
# Simple GET
my $res = $client->get('/');
is $res->status, 200;
is $res->text, 'Hello World';
# GET with query parameters
my $res = $client->get('/search', query => { q => 'perl' });
# POST with JSON body
my $res = $client->post('/api/users', json => { name => 'John' });
# POST with form data
my $res = $client->post('/login', form => { user => 'admin' });
# Custom headers
my $res = $client->get('/api', headers => { Authorization => 'Bearer xyz' });
# Multiple values for same header/query/form field
my $res = $client->get('/search',
query => { tag => ['perl', 'async'] }, # ?tag=perl&tag=async
headers => { Accept => ['text/html', 'application/json'] },
);
# Arrayref of pairs for explicit ordering
my $res = $client->get('/api',
headers => [['X-Custom', 'first'], ['X-Custom', 'second']],
);
# Multi-value form (checkboxes, multi-select)
my $res = $client->post('/survey',
form => { colors => ['red', 'blue', 'green'] },
);
# Session cookies persist across requests
$client->post('/login', form => { user => 'admin', pass => 'secret' });
my $res = $client->get('/dashboard'); # authenticated!
DESCRIPTION
PAGI::Test::Client allows you to test PAGI applications without starting a real server. It invokes your app directly by constructing the PAGI protocol messages ($scope, $receive, $send), making tests fast and simple.
This is inspired by Starlette's TestClient but adapted for Perl and PAGI's specific features like first-class SSE support.
CONSTRUCTOR
new
my $client = PAGI::Test::Client->new(
app => $app, # Required: PAGI app coderef
headers => { ... }, # Optional: default headers
lifespan => 1, # Optional: enable lifespan (default: 0)
);
Options
- app (required)
-
The PAGI application coderef to test.
- headers
-
Default headers to include in every request. Supports multiple formats:
# Simple hash (single values) headers => { 'X-API-Key' => 'secret' } # Hash with arrayref values (multiple values per header) headers => { Accept => ['application/json', 'text/html'] } # Arrayref of pairs (explicit ordering) headers => [['Accept', 'application/json'], ['Accept', 'text/html']]Request-specific headers with the same name will replace (not append to) these default headers.
- lifespan
-
If true, the client will send lifespan.startup when started and lifespan.shutdown when stopped. Default is false (most tests don't need it).
HTTP METHODS
All HTTP methods return a PAGI::Test::Response object.
get
my $res = $client->get($path, %options);
post
my $res = $client->post($path, %options);
put
my $res = $client->put($path, %options);
patch
my $res = $client->patch($path, %options);
delete
my $res = $client->delete($path, %options);
head
my $res = $client->head($path, %options);
options
my $res = $client->options($path, %options);
Request Options
- headers => { ... } or [ [...], [...] ]
-
Additional headers for this request. Supports multiple formats:
# Simple hash headers => { Authorization => 'Bearer xyz' } # Multiple values (arrayref in hash) headers => { Accept => ['application/json', 'text/html'] } # Arrayref of pairs (preserves order) headers => [['X-Custom', 'first'], ['X-Custom', 'second']]Request headers with the same name as client default headers will replace the defaults (not append).
- query => { ... } or [ [...], [...] ]
-
Query string parameters. Supports multiple formats:
# Simple hash query => { q => 'perl' } # Multiple values query => { tag => ['perl', 'async'] } # ?tag=perl&tag=async # Arrayref of pairs query => [['tag', 'perl'], ['tag', 'async']]Note: Query params are appended to any existing query string in the path. To avoid duplicates, put all params either in the path or in the query option, not both with the same key.
- json => { ... }
-
JSON request body. Automatically sets Content-Type to application/json.
- form => { ... } or [ [...], [...] ]
-
Form-encoded request body. Sets Content-Type to application/x-www-form-urlencoded. Supports multiple formats:
# Simple hash form => { user => 'admin', pass => 'secret' } # Multiple values (checkboxes, multi-select) form => { colors => ['red', 'blue', 'green'] } # Arrayref of pairs form => [['color', 'red'], ['color', 'blue']] - body => $bytes
-
Raw request body bytes.
SESSION METHODS
cookies
my $hashref = $client->cookies;
Returns all current session cookies.
cookie
my $value = $client->cookie('session_id');
Returns a specific cookie value.
set_cookie
$client->set_cookie('theme', 'dark');
Manually sets a cookie.
clear_cookies
$client->clear_cookies;
Clears all session cookies.
WEBSOCKET
websocket
# Callback style (auto-close)
$client->websocket('/ws', sub {
my ($ws) = @_;
$ws->send_text('hello');
is $ws->receive_text, 'echo: hello';
});
# Explicit style
my $ws = $client->websocket('/ws');
$ws->send_text('hello');
is $ws->receive_text, 'echo: hello';
$ws->close;
# With options
my $ws = $client->websocket('/ws',
headers => { Authorization => 'Bearer xyz' },
subprotocols => ['chat', 'json'],
);
# Options with callback
$client->websocket('/ws', headers => { 'X-Token' => 'abc' }, sub {
my ($ws) = @_;
# ...
});
See PAGI::Test::WebSocket for the WebSocket connection API.
SSE (Server-Sent Events)
sse
# Callback style (auto-close)
$client->sse('/events', sub {
my ($sse) = @_;
my $event = $sse->receive_event;
is $event->{data}, 'connected';
});
# Explicit style
my $sse = $client->sse('/events');
my $event = $sse->receive_event;
$sse->close;
# With headers (e.g., for reconnection)
my $sse = $client->sse('/events',
headers => { 'Last-Event-ID' => '42' },
);
# Options with callback
$client->sse('/events', headers => { Authorization => 'Bearer xyz' }, sub {
my ($sse) = @_;
# ...
});
See PAGI::Test::SSE for the SSE connection API.
LIFESPAN
start
$client->start;
Triggers lifespan.startup. Only needed if lifespan = 1> was passed to the constructor.
stop
$client->stop;
Triggers lifespan.shutdown.
state
my $state = $client->state;
Returns the shared state hashref from lifespan.
run
PAGI::Test::Client->run($app, sub {
my ($client) = @_;
# ... tests ...
});
Class method that creates a client with lifespan enabled, calls start, runs your callback, then calls stop. Exceptions propagate.
SEE ALSO
PAGI::Test::Response, PAGI::Test::WebSocket, PAGI::Test::SSE