Skip to content

Instantly share code, notes, and snippets.

@marcusramberg
Created October 2, 2010 13:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marcusramberg/607640 to your computer and use it in GitHub Desktop.
Save marcusramberg/607640 to your computer and use it in GitHub Desktop.
=encoding utf8
=head1 NAME
Mojolicious::Guides::Testing - TDD gives you Mojo.
=head1 OVERVIEW
Testing your L<Mojolicious> application.
=head1 CONCEPTS
unit tests, client side code, integration tests
=head1 The Basics
=head2 Testing your Mojolicious Application
Rather than being just another buzz word, Test Driven Development is a radical
change in the way we develop software. In order to allow easy testing of your
Mojolicious application, we include Test::Mojo, a way to perform integration
tests directly against your application. This works well with the
standard Test::More suite used in most perl distributions:
use Test::More tests=>3;
use Mojo::Test;
my $t=Mojo::Test->new(app => 'MyApp');
$t->get_ok('/')
->status_is(200)
->content_like(qr/Hello/);
Note that the test methods are chained for your convenience. If you need to
access the request and response objects for further testing,you can get to
them through $t->tx (short for transaction). L<Test::Mojo> also supports a lot
of other ways to test your request. See the POD for L<Test::Mojo> for all
the methods you can use to test your application.
=head2 Testing your Mojolicious::Lite Application.
Since a L<Mojolicious::Lite> application is a script rather than a package, we
need to do a little extra work to load it in our t/ files:
use FindBin;
$ENV{MOJO_HOME} = "$FindBin::Bin/../";
require "$ENV{MOJO_HOME}/myapp.pl";
my $t = Test::Mojo->new;
=head1 Testing the Parts
=head2 Using Mojo::DOM for client-testing
Recent versions of Mojo include L<Mojo::DOM>, a liberal CSS-selector based
liberal XML parser. In practice, it can even parse most HTML-documents,
making it ideal for testing the interface of your application.
Mojo::DOM uses the same CSS selectors as jQuery to target your
javascript actions. The implementation is so similar we recommend you
refer to their documentation at api.jquery.org/category/selectors/ for
a better understanding of the syntax.
Of course, being a server side implementation, there are some things that
weren't useful to implement in Mojo::DOM, notably the :animated selector and
the :enabled selector. There are also some convenience selectors in jquery
that hasn't been implemented yet in L<Mojo::DOM>.
Still, the implementation is quite full featured, including pseudo
operators like ':checked' and ':empty', which can be quite useful when
testing your forms. You can even do things like ':checked[value="foo"]'.
Mojo also includes support for selector groups and ':first-child' and
even ':nth-child'.
Mojo::Test has some testing methods which let you leverage Mojo::DOM
in your unit tests, including $t->element_exists, $t->text_is and
$t->text_like
Here's a simple example:
$t->get_ok('/')
->element_exists('.header h3','Check existence of header title')
->text_is('.foo:checked'),'groovy','Check text for checked element')
Of course, if you want to write proper unit tests for the user interface
you could use L<MojoX::Renderer> directly to render templates with mock
values, and parse them through Mojo::DOM to test the content. To set this
up, you can create a blank Mojo controller and set mock stash values:
my $c = MojoX::Dispatcher::Routes::Controller->new(app => Mojo->new);
$c->stash->{template} = 'testing';
...
$c->stash->{value1} = 'foo';
is_deeply [$r->render($c)], ['Hello Mojo!', 'text/plain'], 'desc';
However, doing it this way stops you from using Test::Mojo's convenience
methods.
=head2 Testing your Web Service
If your web service is XML based, you can use the same methods as in the
previous section to test your XML structures with ease. Mojo::DOM is not
just for HTML, you can use it for valid XML structure.
If you prefer JSON (like most of us), L<Test::Mojo> provides a json_content_is
method to test your generated data structures. This works about the same
as is_deeply:
$t->json_content_is(['foo','bar',{baz => 'quz'}]);
Also, if you are writing a RESTish web service, remember to test your
status codes and different verbs as well.
=head2 Testing your Model
Of course, in every well structured application, most of your logic should
reside in the model layer. Mojolicious is model-agnostic to the point of
not even including Model-adapters like you might be used to from Catalyst.
This means that you can test your data models completely separate from the
rest of your Mojolicious application. Just write unit tests as you would
normally do.
=head2 Going low-level.
Some times you need to test things at the wire level. Taking a page out
of the Mojo playbook, here's how Sebastian does this in the Mojo test
suite itself. First, these tests require us to set up a socket, so we
need to make sure we have a working socket implementation.
plan skip_all => 'working sockets required for this test!'
unless Mojo::IOLoop->new->generate_port;
It's convenient to set up a Mojolicious::Lite app to use for testing.
Just create it as normal, then instead of starting it, we create a
simple server.
my $client = Mojo::Client->singleton->app(app);
my $port = $client->ioloop->generate_port;
my $id = $client->ioloop->listen(
port => $port,
on_accept => sub { ... },
on_read => sub { ... },
on_error => sub { ... },
);
In the callback methods, you can implement custom behaviour needed for
your tests. Then you just use the client to make requests against the
server.
$tx = $client->get("http://localhost:$port/mock");
Then use normal Test::More methods to test your $tx->res.
=head2 Testing Web Sockets
Testing web sockets can be accomplished fairly easily using the built-in
L<Mojo::Client>. Load your web socket using app and pass it to a Mojo::Client
my $client = Mojo::Client->new->app($app);
Since a web socket doesn't really follow a request response model we can't use
L<Mojo::Test>. Here's a simple example of how to test an interaction.
my $req_number = 0;
$client->websocket(
# First request
'/' => sub {
my $self = shift;
$self->receive_message(
sub {
my ($self, $message) = @_;
if ($req_number == 0) { # first request
like($message, qr/{"text":"Guest\d+ has joined chat"}/);
} elsif
...
# end of last message
$self->finish;
}
$req_number++;
});
This technique can also be combined with a set of $self->send_message method
calls to simulate a full conversation. notice the $self->finish at the end of
the last message to end the conversation.
=cut
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment