Testing a Dancer application
In the previous article, we saw how to develop a plugin. We have written our example as a plugin named Dancer::Plugin::MobileDevice. In this article We'll see how to test it.
As testing a plugin is pretty much the same thing as testing a complete application, you will learn in this article how to use the Dancer::Test module for building a good test suite for a Dancer application.
Why testing?
Writing a plugin for the Dancer ecosystem is a great thing to do; It's a very good way to contribute to the project, but writing the plugin is not enough, you should write tests to validate your plugin before releasing it to the CPAN.
We will now look at how easy it is to do that and we'll write a test suite to make sure everything provided by the plugin works as expected. Remember that tests are still code. Take quality seriously and your software will be better.
The is_mobile_device
keyword
Let's start by testing that our helper works as expected, with Dancer::Test this is easy to do:
# t/01-is_mobile_device.t use strict; use warnings; use Test::More import => ['!pass'];
Note that we ask not to import Test::More's pass
keyword.
It's already exported by Dancer and we don't want our test script to
produce a warning.
First of all, we need a basic app that uses our plugin, we'll define it within the test script, inside a lexical block:
{ use Dancer; use Dancer::Plugin::MobileDevice; get '/' => sub { return is_mobile_device; }; }
OK, we have a basic app that just loads the plugin and defines one
route handler which only returns the value of is_mobile_device
.
That's enough, we can now write the tests.
use Dancer::Test;
Dancer::Test provides a complete set of test functions specialized for
testing a Dancer application. Here we'll use the function
response_content_is
which takes a request object (basically an
array with a method and a path) and a value, and makes sure the route
handler returns a response that is the same as the expected value.
We'll define a bunch of user agent strings we consider "mobile devices" and make sure the flag is set appropriately for them:
my @mobile_devices = qw(Android iPhone PalmOS); for my $md (@mobile_devices) { $ENV{HTTP_USER_AGENT} = $md; response_content_is [GET => '/'], 1, "agent $md is a mobile device"; }
And we finally add a non-mobile string:
$ENV{HTTP_USER_AGENT} = 'Mozilla'; response_content_is [GET => '/'], 0, "Mozilla is not a mobile device";
Let's run the test:
$ perl -Ilib t/01-is_mobile_device.t 1..4 ok 1 - agent iPhone is a mobile device ok 2 - agent Android is a mobile device ok 3 - agent PalmOS is a mobile device ok 4 - Mozilla is not a mobile device
Great! We know now for sure that is_mobile_device works
, that's a good
start!
The default template token
We now want to make sure all of our template calls got the
is_mobile_device
token. To do that, our test application will now
only provide a route handler that calls template
. Obviously, for
this to work we need... a template to process. Dancer::Test takes
care for us to initialize the views directory to t/views, so we can
provide our test script with some views without polluting the root
directory of our distribution.
So we first create a view:
$ mkdir t/views $ echo "is_mobile_device: <% is_mobile_device %>" > t/views/index.tt
The view is very basic, it just shows the interpolation of the
is_mobile_device
token. Let's use it in our test script.
{ use Dancer; use Dancer::Plugin::MobileDevice; get '/' => sub { template 'index', {}, { layout => undef }; }; }
Same as previously, we define a route handler that does just what we
need. You'll see that we've given some extra options to the
template
keyword in order to disable the layout. Indeed, our plugin
automatically sets a layout for mobile clients and we don't want to
see that for the moment.
The second argument given to template
is an empty hash which is
actually the tokens hash. It should have been populated by our plugin
under the hood and we'll make sure of that.
use Dancer::Test; $ENV{HTTP_USER_AGENT} = 'Android'; response_content_is [GET => '/'], "is_mobile_device: 1\n", "token is_mobile_device is present and valid for Android"; $ENV{HTTP_USER_AGENT} = 'Mozilla'; response_content_is [GET => '/'], "is_mobile_device: 0\n", "token is_mobile_device is present and valid for Mozilla";
Let's run the test to make sure everything is fine:
$ perl -Ilib t/02-tokens.t 1..2 ok 1 - token is_mobile_device is present and valid for Android ok 2 - token is_mobile_device is present and valid for Mozilla
Cool! We now have one more thing to test: making sure the layout changes appropriately, the job of our final test script.
The dynamic layout
In this final test we want to be sure the layout is changed to 'mobile' whenever a mobile device is served, and that the original layout is reset afterwards (whether it was defined or not).
To do that we'll use the same technique as before, but this time with the layouts:
$ mkdir t/views/layouts $ echo -e "mobile:\n<% content %>" > t/views/layout/mobile.tt $ echo -e "main:\n<% content %>" > t/views/layout/main.tt
(or use your favourite editor to create our layout files)
We now have our layouts waiting to be used in the t/views/layouts
directory, let's use it.
{ use Dancer; use Dancer::Plugin::MobileDevice; get '/' => sub { template 'index'; }; }
Just a basic route handler, like before, but this time if a layout is set, we'll use it.
First, we want to test the behaviour of the app when no layout is set:
use Dancer::Test; $ENV{HTTP_USER_AGENT} = 'Android'; response_content_like [GET => '/'], qr{mobile\nis_mobile_device: 1}ms, "mobile layout is set for mobile agents"; $ENV{HTTP_USER_AGENT} = 'Mozilla'; response_content_is [GET => '/'], "is_mobile_device: 0\n", "no layout for non-mobile agents";
And then, when a layout is manually set by the user:
set layout => 'main'; $ENV{HTTP_USER_AGENT} = 'Android'; response_content_like [GET => '/'], qr{mobile\nis_mobile_device: 1}ms, "mobile layout is set for mobile agents"; $ENV{HTTP_USER_AGENT} = 'Mozilla'; response_content_like [GET => '/'], qr{main\nis_mobile_device: 0}ms, "main layout for non-mobile agents";
Let's see if everything works:
$ perl -Ilib t/03-layouts.t ok 1 - mobile layout is set for mobile agents ok 2 - no layout for non-mobile agents ok 3 - mobile layout is set for mobile agents ok 4 - main layout for non-mobile agents
Great, everything works as expected!
Conclusion
Our plugin is now covered by the test suite we've written, and we can publish it to the CPAN. Taking a step further, we could run coverage test with Devel::Cover in order to make sure our test suite goes through all the possible paths but that's beyond the scope of this article.
Author
This article has been written by Alexis Sukrieh.
Copyright
Copyright (C) 2010 by Alexis Sukrieh <sukria@sukria.net>