There is an ongoing outage on the primary CPAN mirror. It is possible to work around the issue by using MetaCPAN as a mirror.

NAME

Dancer2::Manual::Testing - Writing tests for Dancer2

VERSION

version 2.0.0

Basic application testing

Since Dancer2 produces PSGI applications, you can easily write tests using Plack::Test and provide your Dancer application as the app for testing.

A basic test (which we also scaffold with dancer2) looks like this:

use strict;
use warnings;

use Test::More tests => 4;
use Plack::Test;
use HTTP::Request::Common;

use_ok('MyApp');

# create an application
my $app = MyApp->to_app;
isa_ok( $app, 'CODE' );

# create a testing object
my $test = Plack::Test->create($app);

# now you can call requests on it and get responses
# requests are of HTTP::Request
# responses are of HTTP::Response

# "GET" from HTTP::Request::Common creates an HTTP::Request object
my $response = $test->request( GET '/' );

# same as:
# my $response = $test->request( HTTP::Request->new( GET => '/' ) );

ok( $response->is_success, 'Successful request' );
is( $response->content, 'OK', 'Correct response content' );

Read the documentation for HTTP::Request and HTTP::Request::Common to see the different options for sending parameters.

Subtests

Tests can be separated using Test::More's subtest functionality, thus creating multiple self-contained tests that don't affect each other.

Assuming we have a different app that we want to test:

# MyApp.pm
package MyApp;
use Dancer2;
set serializer => 'JSON';

my %users = (
    jason => {
        name  => 'Jason',
        likes => 'planes',
    },
    yanick => {
        name => 'Yanick',
        likes => 'orchids',
    }
);

get '/:user' => sub {
    my $user = route_parameters->get('user');

    my $user_data = $users{$user};

    if( $user_data ) {
        return { user => $user_data };
    }

    status 404;

    return {
        error => 1,
        message => 'user not found'
    }
};

1;

This is an example of tests for that route ensuring that we have the correct behavior with regard to the user parameter.

# param.t
use strict;
use warnings;

use Test::More;
use Plack::Test;
use HTTP::Request::Common;
use JSON qw/ decode_json /;

use MyApp;

# swap 'null' for 'note' to see the logs in your TAP output
MyApp::set( logger => 'null' );

my $test = Plack::Test->create( MyApp->to_app );

subtest 'An empty request' => sub {
    my $res = $test->request( GET '/' );
    is $res->code => 404, 'user not provided, so not found';
};

subtest 'Request with invalid user' => sub {
    my $res = $test->request( GET '/sawyer_x' );

    ok !$res->is_success, 'user not found';
    is $res->code => 404, 'ressource not found';

    is decode_json($res->content)->{message}, 'user not found', 'error message';
};

subtest 'Request with valid user' => sub {
    my $res = $test->request( GET '/jason' );

    ok $res->is_success, 'user found';

    is decode_json($res->content)->{user}{likes}, 'planes', 'data present';
};

done_testing();

Cookies

To handle cookies, which are mostly used for maintaining sessions, the following modules can be used:

Taking the previous test, assuming it actually creates and uses cookies for sessions (see cookie and perhaps Dancer2::Session::Cookie for more information on how to do that):

# ... all the use statements
use HTTP::Cookies;

my $jar  = HTTP::Cookies->new;
my $test = Plack::Test->create( MyApp->to_app );

subtest 'Request with invalid user' => sub {
    my $req = GET '/sawyer_x';
    $jar->add_cookie_header($req);
    my $res = $test->request($req);
    ok !$res->is_success, 'Request failed';
    $jar->extract_cookies($res);

    ok !$jar->as_string, 'All cookies deleted';
};

subtest 'Request with user' => sub {
    my $req = GET '/jason';
    $jar->add_cookie_header($req);
    my $res = $test->request($req);
    ok $res->is_success, 'Successful request';
    $jar->extract_cookies($res);

    ok !$jar->as_string, 'All cookies deleted';
};

done_testing();

If you don't want to use an entire user agent for this test, you can use HTTP::Cookies to store cookies and then retrieve them:

use strict;
use warnings;

use Test::More tests => 3;
use Plack::Test;
use HTTP::Request::Common;
use HTTP::Cookies;

use_ok('MyApp');

my $url  = 'http://localhost';
my $jar  = HTTP::Cookies->new();
my $test = Plack::Test->create( MyApp->to_app );

subtest 'Create session' => sub {
    my $res = $test->request( GET "$url/login" );
    ok( $res->is_success, 'Successful login' );

    # extract cookies from the response and store in the jar
    $jar->extract_cookies($res);
};

subtest 'Check session' => sub {
    my $req = GET "$url/logout";

    # add cookies to the request
    $jar->add_cookie_header($req);

    my $res = $test->request($req);
    ok( $res->is_success, 'Successful logout' );
    like(
        $res->content,
        'Successfully logged out',
        'Got correct log out content',
    );
};

Please note that the request URL must include scheme and host for the call to "add_cookie_header" in HTTP::Cookies to work.

Accessing the configuration file

By importing Dancer2 in the test files, there is full access to the configuration using the imported keywords:

use strict;
use warnings;
use Test::More;
use Plack::Test;
use HTTP::Request::Common;
use MyApp;
use Dancer2;

my $appname = config->{'appname'};
diag "Testing $appname";

# ...

Plugins

In order to test plugins, you can create an application on the spot, as part of the test script code, and use the plugin there.

use strict;
use warnings;

use Test::More tests => 2;
use Plack::Test;
use HTTP::Request::Common;

{
    package MyTestApp;
    use Dancer2;
    use Dancer2::Plugin::MyPlugin;

    get '/' => sub { my_keyword };
}

my $test = Plack::Test->create( MyTestApp->to_app );
my $res  = $test->request( GET '/' );

ok( $res->is_success, 'Successful request' );
is( $res->content, 'MyPlugin-MyKeyword', 'Correct content' );

AUTHOR

Dancer Core Developers

COPYRIGHT AND LICENSE

This software is copyright (c) 2025 by Alexis Sukrieh.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.