Skip to content
yuki-kimoto edited this page Mar 7, 2011 · 14 revisions

Testing Mojolicious applications

Mojolicious::Guides::Testing - TDD gives you Mojo.

OVERVIEW

Testing your Mojolicious application.

CONCEPTS

unit tests, client side code, integration tests

The Basics

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 Test::Mojo;

my $t=Test::Mojo->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). Test::Mojo also supports a lot of other ways to test your request. See the POD for Test::Mojo for all the methods you can use to test your application.

Testing your Mojolicious::Lite Application.

Since a 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 Test::More;
use Test::Mojo;

use FindBin;
$ENV{MOJO_HOME} = "$FindBin::Bin/../";
require "$ENV{MOJO_HOME}/myapp.pl";

my $t = Test::Mojo->new;
$t->get_ok('/myaction');
done_testing;

Run tests

Run your script by test from command line to command to run tests.

perl myapp.pl test

Test config.

When doing integration tests for your app, it can be useful to override parts of the config. For instance, you might want your tests to use a testing database.

You can easily do this by setting the app mode to testing.

$ENV{MOJO_MODE} = "testing";

Then simply provide a config with the mode name. For instance, if your config file is myapp.json, you provide a myapp.testing.json.

Testing the Parts

Using Mojo::DOM for client-testing

Recent versions of Mojo include Mojo::DOM, a simple CSS3-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 http://api.jquery.com/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 Mojo::DOM. In general, Mojo::DOM implements the strict CSS3 syntax.

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'.

Test::Mojo 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 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 prevents you from using Test::Mojo's convenience methods.

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), 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 RESTful web service, remember to test your status codes and the different verbs (GET, PUT etc.) as well.

$t->delete_ok('/foo/bar')
  ->status_is(200);

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 adapters for Model 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.

Going Low-Level

Sometimes you need to test things at the wire level. Taking a page out of the Mojo's 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 application 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.

Testing Web Sockets

Testing web sockets can be accomplished fairly easily using the built-in 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 Test::Mojo. 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.

Developing with Test Driven Development in Mind

If you have an existing application and you are planning to add some tests before deployment or introduction of modifications, you should plan to find a number of bugs. Good tests test for all the common use-cases, but also for the less common or the weird: the corner cases. Therefore writing a good test suite for an existing application can be a long process, but it should allow to fix many difficult-to find bugs and to create a safety net for future development. -In fact, once a testing suite exists, it becomes a very important part of your development and maintenance process. Modification and refactoring of the code can be more rapid since the tests make sure no part of the functionality has been broken by the changes.

In fact, if you truly use Test Driven Development, every bug found and every feature planned should start with appropriate tests. Once the test pass, the bug is solved or the feature implemented. In this way, your test suite will not be only a quality assurance tool and safety net against new bugs, it will become the experiment and testing ground for your development.

And for that reason, the only correct way to submit a bug report or a feature request for Mojolicious is: submitting a new test.

Useful Tips

Avoiding lots of log output when running tests

To make your tests less noisy you can also change the application log level directly in your test files.

$t->app->log->level('error');

Get application object

If you want to get application object, it is a little difficult, so it is good that you set app to application object, not application class.

use MyApp;
my $app = MyApp->new;

use Test::Mojo;
my $t = Test::Mojo->new(app => $app);

Extract test failing line

HTML output is very large, so test result is often unreadable. You can extract test failing line using pipe and grep command.

perl myapp.pl test 2>&1 | grep line

SEE ALSO

Test::Mojo, Test::More, examples under t/ in the Mojolicious distribution.

Clone this wiki locally