Skip to content

Commit

Permalink
redesigned WebSocket message testing
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Jan 15, 2013
1 parent 7615e0b commit 50a639b
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 44 deletions.
5 changes: 5 additions & 0 deletions Changes
@@ -1,9 +1,14 @@

3.80 2013-01-15
- Deprecated testing WebSocket messages without calling
Test::Mojo->message_ok.
- Deprecated Mojo::Util->html_escape in favor of Mojo::Util->xml_escape.
- Deprecated Mojo::ByteStream->html_escape in favor of
Mojo::ByteStream->xml_escape.
- Deprecated Mojo::Home->slurp_rel_file in favor of Mojo::Util->slurp.
- Added message attribute to Test::Mojo.
- Added json_message_has, json_message_hasnt and message_ok methods to
Test::Mojo.
- Improved documentation.
- Improved tests.
- Fixed support for multi byte entities in Mojo::Util.
Expand Down
1 change: 1 addition & 0 deletions lib/Mojolicious/Guides/Cookbook.pod
Expand Up @@ -512,6 +512,7 @@ L<Test::Mojo> API to be used.
my $t = Test::Mojo->new;
$t->websocket_ok('/echo')
->send_ok('Hello Mojo!')
->message_ok
->message_is('echo: Hello Mojo!')
->finish_ok;

Expand Down
86 changes: 74 additions & 12 deletions lib/Test/Mojo.pm
Expand Up @@ -16,7 +16,7 @@ use Mojo::UserAgent;
use Mojo::Util qw(decode encode);
use Test::More ();

has 'tx';
has [qw(message tx)];
has ua => sub { Mojo::UserAgent->new->ioloop(Mojo::IOLoop->singleton) };

# Silent or loud tests
Expand Down Expand Up @@ -164,12 +164,22 @@ sub json_is {
return $self->_test('is_deeply', $self->tx->res->json($p), $data, $desc);
}

sub json_message_has {
my ($self, $p, $desc) = @_;
$desc ||= qq{has value for JSON Pointer "$p"};
return $self->_test('ok', $self->_json(contains => $p), $desc);
}

sub json_message_hasnt {
my ($self, $p, $desc) = @_;
$desc ||= qq{has no value for JSON Pointer "$p"};
return $self->_test('ok', !$self->_json(contains => $p), $desc);
}

sub json_message_is {
my ($self, $p, $data, $desc) = @_;
my $value = Mojo::JSON::Pointer->new->get(
Mojo::JSON->new->decode(@{$self->_next || []}[1]), $p);
return $self->_test('is_deeply', $value, $data,
$desc || 'exact match for JSON structure');
$desc ||= qq{exact match for JSON Pointer "$p"};
return $self->_test('is_deeply', $self->_json(get => $p), $data, $desc);
}

sub message_is {
Expand All @@ -187,6 +197,11 @@ sub message_like {
return $self->_message('like', $regex, $desc || 'message is similar');
}

sub message_ok {
my ($self, $desc) = @_;
return $self->_test('ok', !!$self->_wait(1), $desc, 'message received');
}

sub message_unlike {
my ($self, $regex, $desc) = @_;
return $self->_message('unlike', $regex, $desc || 'message is not similar');
Expand Down Expand Up @@ -302,10 +317,16 @@ sub _get_content {
return $charset ? decode($charset, $content) : $content;
}

sub _json {
my ($self, $method, $p) = @_;
return Mojo::JSON::Pointer->new->$method(
Mojo::JSON->new->decode(@{$self->_wait || []}[1]), $p);
}

sub _message {
my ($self, $name, $value, $desc) = @_;
local $Test::Builder::Level = $Test::Builder::Level + 1;
my ($type, $msg) = @{$self->_next || ['']};
my ($type, $msg) = @{$self->_wait || ['']};

# Type check
if (ref $value eq 'HASH') {
Expand All @@ -320,12 +341,6 @@ sub _message {
return $self->_test($name, $msg // '', $value, $desc);
}

sub _next {
my $self = shift;
Mojo::IOLoop->one_tick while !$self->{finished} && !@{$self->{messages}};
return shift @{$self->{messages}};
}

sub _request_ok {
my ($self, $method, $url, $headers, $body) = @_;
$body = $headers if !ref $headers && @_ > 3;
Expand All @@ -351,6 +366,18 @@ sub _text {
return $e->text;
}

# DEPRECATED in Rainbow!
sub _wait {
my ($self, $wait) = @_;
my $new = $self->{new} //= $wait;
warn <<EOF unless $new;
Testing WebSocket messages without Test::Mojo->message_ok is DEPRECATED!!!
EOF
return $self->message if $new && !$wait;
Mojo::IOLoop->one_tick while !$self->{finished} && !@{$self->{messages}};
return $self->message(shift @{$self->{messages}})->message;
}

1;

=head1 NAME
Expand All @@ -377,6 +404,7 @@ Test::Mojo - Testing Mojo!
# WebSocket
$t->websocket_ok('/echo')
->send_ok('hello')
->message_ok
->message_is('echo: hello')
->finish_ok;
Expand All @@ -391,6 +419,18 @@ L<Mojo> and L<Mojolicious> applications.
L<Test::Mojo> implements the following attributes.
=head2 message
my $msg = $t->message;
$t = $t->message([text => $bytes]);
Current WebSocket message.
# Test custom message
$t->message([binary => $bytes])
->json_message_has('/foo')
->json_message_is('/foo/bar' => {baz => 'yada'});
=head2 tx
my $tx = $t->tx;
Expand Down Expand Up @@ -624,6 +664,21 @@ Opposite of C<json_has>.
Check the value extracted from JSON response using the given JSON Pointer with
L<Mojo::JSON::Pointer>.
=head2 json_message_has
$t = $t->json_message_has('/foo');
$t = $t->json_message_has('/minibar', 'has a minibar');
Check if JSON WebSocket message contains a value that can be identified using
the given JSON Pointer with L<Mojo::JSON::Pointer>.
=head2 json_message_hasnt
$t = $t->json_message_hasnt('/foo');
$t = $t->json_message_hasnt('/minibar', 'no minibar');
Opposite of C<json_message_has>.
=head2 json_message_is
$t = $t->json_message_is('/' => {foo => [1, 2, 3]});
Expand Down Expand Up @@ -660,6 +715,13 @@ Opposite of C<message_is>.
Check WebSocket message for similar match.
=head2 message_ok
$t = $t->message_ok;
$t = $t->message_ok('got a message');
Wait for next WebSocket message to arrive.
=head2 message_unlike
$t = $t->message_unlike({binary => qr/$bytes/});
Expand Down
78 changes: 46 additions & 32 deletions t/mojolicious/websocket_lite_app.t
Expand Up @@ -104,66 +104,74 @@ my $t = Test::Mojo->new;

# Default protocol
$t->websocket_ok('/echo')->header_is('Sec-WebSocket-Protocol' => 'mojo')
->send_ok('hello')->message_is('echo: hello')->finish_ok;
->send_ok('hello')->message_ok('got a message')->message_is('echo: hello')
->finish_ok;

# Multiple roundtrips
$t->websocket_ok('/echo')->send_ok('hello again')
->message_is('echo: hello again')->send_ok('and one more time')
->message_is('echo: and one more time')->finish_ok;
->message_ok->message_is('echo: hello again')->send_ok('and one more time')
->message_ok->message_is('echo: and one more time')->finish_ok;

# Custom protocol
$t->websocket_ok('/echo', {'Sec-WebSocket-Protocol' => 'foo, bar, baz'})
->header_is('Sec-WebSocket-Protocol' => 'foo')->send_ok('hello')
->message_is('echo: hello')->finish_ok;
->message_ok->message_is('echo: hello')->finish_ok;

# Bytes
$t->websocket_ok('/echo')->send_ok({binary => 'bytes!'})
->message_is({binary => 'bytes!'})->send_ok({binary => 'bytes!'})
->message_isnt({text => 'bytes!'})->finish_ok;
->message_ok->message_is({binary => 'bytes!'})
->send_ok({binary => 'bytes!'})
->message_ok->message_isnt({text => 'bytes!'})->finish_ok;

# Zero
$t->websocket_ok('/echo')->send_ok(0)->message_is('echo: 0')->send_ok(0)
->message_like({text => qr/0/})->finish_ok;
$t->websocket_ok('/echo')->send_ok(0)->message_ok->message_is('echo: 0')
->send_ok(0)->message_ok->message_like({text => qr/0/})->finish_ok;

# Plain alternative
$t->get_ok('/echo')->status_is(200)->content_is('plain echo!');

# JSON roundtrips
$t->websocket_ok('/json')
->send_ok({text => j({test => 23, snowman => ''})})
->json_message_is('/' => {test => 24, snowman => ''})
->message_ok->json_message_is('/' => {test => 24, snowman => ''})
->json_message_has('/test')->json_message_hasnt('/test/2')
->send_ok({binary => j([1, 2, 3])})
->message_ok->json_message_is('/' => [1, 2, 3, 4], 'right content')
->send_ok({binary => j([1, 2, 3])})
->json_message_is('/' => [1, 2, 3, 4], 'right content')
->send_ok({binary => j([1, 2, 3])})->json_message_is('/2' => 3)->finish_ok;
->message_ok->json_message_has('/2', 'has two elements')
->json_message_is('/2' => 3)->json_message_hasnt('/5', 'not five elements')
->finish_ok;

# Plain request
$t->get_ok('/plain')->status_is(200)->content_is('Nothing to see here!');

# Server push
$t->websocket_ok('/push')->message_is('push')->message_is('push')
->message_is('push')->finish_ok;
$t->websocket_ok('/push')->message_unlike(qr/shift/)->message_isnt('shift')
->message_like(qr/us/)->message_unlike({binary => qr/push/})->finish_ok;
$t->websocket_ok('/push')->message_ok->message_is('push')
->message_ok->message_is('push')->message_ok->message_is('push')->finish_ok;
$t->websocket_ok('/push')->message_ok->message_unlike(qr/shift/)
->message_ok->message_isnt('shift')->message_ok->message_like(qr/us/)
->message_ok->message_unlike({binary => qr/push/})->finish_ok;

# Another plain request
$t->get_ok('/plain')->status_is(200)->content_is('Nothing to see here!');

# Multiple roundtrips
$t->websocket_ok('/echo')->send_ok('hello')->message_is('echo: hello')
->finish_ok;
$t->websocket_ok('/echo')->send_ok('hello')
->message_ok->message_is('echo: hello')->finish_ok;
$t->websocket_ok('/echo')->send_ok('this')->send_ok('just')->send_ok('works')
->message_is('echo: this')->message_is('echo: just')
->message_is('echo: works')->finish_ok;
->message_ok->message_is('echo: this')->message_ok->message_is('echo: just')
->message_ok->message_is('echo: works')->message_like(qr/orks/)->finish_ok;

# Another plain request
$t->get_ok('/plain')->status_is(200)->content_is('Nothing to see here!');

# Unicode roundtrips
$t->websocket_ok('/unicode')->send_ok('hello')->message_is('♥: hello')
->finish_ok;
$t->websocket_ok('/unicode')->send_ok('hello')
->message_ok->message_is('♥: hello')->finish_ok;
$t->websocket_ok('/unicode')->send_ok('hello again')
->message_is('♥: hello again')->send_ok('and one ☃ more time')
->message_is('♥: and one ☃ more time')->finish_ok;
->message_ok->message_is('♥: hello again')
->send_ok('and one ☃ more time')
->message_ok->message_is('♥: and one ☃ more time')->finish_ok;

# Binary frame and frame event
my $bytes = b("I ♥ Mojolicious")->encode('UTF-16LE')->to_string;
Expand All @@ -175,28 +183,34 @@ $t->tx->on(
$binary++ if $frame->[4] == 2;
}
);
$t->send_ok({binary => $bytes})->message_is($bytes);
$t->send_ok({binary => $bytes})->message_ok->message_is($bytes);
ok $binary, 'received binary frame';
$binary = undef;
$t->send_ok({text => $bytes})->message_is($bytes)->finish_ok;
$t->send_ok({text => $bytes})->message_ok->message_is($bytes)->finish_ok;
ok !$binary, 'received text frame';

# Binary roundtrips
$t->websocket_ok('/bytes')->send_ok({binary => $bytes})->message_is($bytes)
->send_ok({binary => $bytes})->message_is($bytes)->finish_ok;
$t->websocket_ok('/bytes')->send_ok({binary => $bytes})
->message_ok->message_is($bytes)->send_ok({binary => $bytes})
->message_ok->message_is($bytes)->finish_ok;

# Two responses
$t->websocket_ok('/once')->send_ok('hello')->message_is('ONE: hello')
->message_is('TWO: hello')->send_ok('hello')->message_is('ONE: hello')
->send_ok('hello')->message_is('ONE: hello')->finish_ok;
$t->websocket_ok('/once')->send_ok('hello')
->message_ok->message_is('ONE: hello')->message_ok->message_is('TWO: hello')
->send_ok('hello')->message_ok->message_is('ONE: hello')->send_ok('hello')
->message_ok->message_is('ONE: hello')->finish_ok;

# Nested WebSocket
$t->websocket_ok('/nested')->send_ok('hello')
->message_is('nested echo: hello')->finish_ok;
->message_ok->message_is('nested echo: hello')->finish_ok;

# Test custom message
$t->message([binary => 'foobarbaz'])->message_like(qr/bar/)
->message_is({binary => 'foobarbaz'});

# Nested WebSocket with cookie
$t->websocket_ok('/nested')->send_ok('hello')
->message_is('nested echo: helloagain')->finish_ok;
->message_ok->message_is('nested echo: helloagain')->finish_ok;

# Nested plain request
$t->get_ok('/nested')->status_is(200)->content_is('plain nested!');
Expand Down

0 comments on commit 50a639b

Please sign in to comment.