NAME
PAGI::Test::WebSocket - WebSocket connection for testing PAGI applications
SYNOPSIS
use PAGI::Test::Client;
my $client = PAGI::Test::Client->new(app => $ws_app);
# 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;
# JSON convenience
$ws->send_json({ action => 'ping' });
my $data = $ws->receive_json;
DESCRIPTION
PAGI::Test::WebSocket provides a test client for WebSocket connections in PAGI applications. It handles the WebSocket protocol handshake and message exchange, making it easy to test WebSocket endpoints without starting a real server.
This module is typically used via PAGI::Test::Client's websocket method rather than directly.
This module is a simplified in-process model of a WebSocket connection. It is useful for testing application-level message flow, but it does not fully emulate transport timing, protocol validation, or network buffering.
CONSTRUCTOR
new
my $ws = PAGI::Test::WebSocket->new(
app => $app, # Required: PAGI app coderef
scope => $scope, # Required: WebSocket scope hashref
);
Creates a new WebSocket test connection. Typically you don't call this directly; use PAGI::Test::Client's websocket method instead.
METHODS
send_text
$ws->send_text('Hello, server!');
Sends a text message to the WebSocket application.
send_bytes
$ws->send_bytes("\x00\x01\x02\x03");
Sends a binary message to the WebSocket application.
send_json
$ws->send_json({ action => 'ping', id => 123 });
Encodes a Perl data structure as JSON and sends it as a text message.
receive_text
my $text = $ws->receive_text;
my $text = $ws->receive_text($timeout); # custom timeout in seconds
Waits for and returns the next text message from the server. Returns undef if the connection is closed.
Current limitation: this method does not actually block or wait for the timeout duration. If no queued text message is immediately available, it throws an exception right away.
Only returns text messages; binary messages are skipped.
receive_bytes
my $bytes = $ws->receive_bytes;
my $bytes = $ws->receive_bytes($timeout);
Waits for and returns the next binary message from the server. Returns undef if the connection is closed.
Current limitation: this method does not actually block or wait for the timeout duration. If no queued binary message is immediately available, it throws an exception right away.
Only returns binary messages; text messages are skipped.
receive_json
my $data = $ws->receive_json;
my $data = $ws->receive_json($timeout);
Waits for a text message, decodes it as JSON, and returns the resulting Perl data structure. Dies if the message is not valid JSON.
LIMITATIONS
This helper does not simulate real WebSocket framing, network buffering, backpressure, or wire-level timing behavior.
The receive timeout arguments are advisory only at present; receive methods check the current queue immediately rather than waiting asynchronously.
For protocol-compliance, keepalive timing, or transport-level edge cases, test against PAGI::Server and a real WebSocket client.
close
$ws->close;
$ws->close($code);
$ws->close($code, $reason);
Closes the WebSocket connection. Default close code is 1000 (normal closure).
close_code
my $code = $ws->close_code;
Returns the WebSocket close code if the connection has been closed, or undef if still open.
close_reason
my $reason = $ws->close_reason;
Returns the WebSocket close reason if the connection has been closed, or an empty string if still open.
is_closed
if ($ws->is_closed) {
say "Connection closed";
}
Returns true if the WebSocket connection has been closed.
INTERNAL METHODS
_start
$ws->_start;
Internal method called by PAGI::Test::Client to start the WebSocket connection, send the initial connect event, and wait for acceptance.
WEBSOCKET PROTOCOL
This module implements the PAGI WebSocket protocol:
- 1. Test sends
websocket.connectevent - 2. App sends
websocket.acceptevent - 3. Test sends
websocket.receiveevents withtextorbytes - 4. App sends
websocket.sendevents withtextorbytes - 5. Either side sends
websocket.disconnectorwebsocket.close
EXAMPLE
use Test2::V0;
use PAGI::Test::Client;
use Future::AsyncAwait;
# Simple echo WebSocket app
my $ws_app = async sub {
my ($scope, $receive, $send) = @_;
return unless $scope->{type} eq 'websocket';
my $event = await $receive->();
return unless $event->{type} eq 'websocket.connect';
await $send->({ type => 'websocket.accept' });
while (1) {
my $msg = await $receive->();
last if $msg->{type} eq 'websocket.disconnect';
if (defined $msg->{text}) {
await $send->({
type => 'websocket.send',
text => "echo: $msg->{text}"
});
}
}
};
# Test it
my $client = PAGI::Test::Client->new(app => $ws_app);
$client->websocket('/ws', sub {
my ($ws) = @_;
$ws->send_text('hello');
is $ws->receive_text, 'echo: hello', 'echoed text';
});
SEE ALSO
PAGI::Test::Client, PAGI::Test::Response, PAGI::WebSocket
AUTHOR
PAGI Contributors