Dancer2...Testing(3)

✨ Dancer2::Manual::Testing β€” the vibe ✨

okay bestie, so this is Dancer2::Manual::Testing, from the User Contributed Perl Documentation crew, and it's literally your whole guide to writing tests for Dancer2. no cap. πŸ’–


🌸 VERSION 🌸

version 2.2.1 β€” she's current, she's serving. πŸ’…


🧚 Basic application testing 🧚

so here's the tea: Dancer2 produces PSGI applications, which means you can very easily write tests using Plack::Test and just hand it your Dancer app as the app under test. iconic behavior, honestly. πŸ¦‹

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' );

go read the docs for HTTP::Request and HTTP::Request::Common to see all the different options for sending parameters, k? there's a lot going on in there and it's all useful. πŸ‘‘

πŸŽ€ Subtests πŸŽ€

Tests can be separated using Test::More's subtest functionality, which means you get multiple self-contained tests that don't affect each other. it's giving ✨organization✨. no more one test ruining the whole mood.

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, making sure we have the correct behavior with regard to the user parameter. lowkey this is the blueprint fr fr.

# 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:

  • Test::WWW::Mechanize::PSGI
  • LWP::Protocol::PSGI
  • HTTP::Cookies

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 bestie: the request URL must include scheme and host for the call to add_cookie_header in HTTP::Cookies to work. that's not optional, that's the whole trick. πŸ’€

🌈 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. main character move honestly.

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 β€” absolute legends, we stan. 🫢


This software is copyright (c) 2026 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. (yes really, go wild, that's the whole license vibe.)


and that's the whole page, bestie!! this was Dancer2...Testing(3) from perl v5.42.3, dated 2026-09-20, part of the User Contributed Perl Documentation. go forth and test your apps, you've absolutely got this. πŸ’–βœ¨πŸŽ€πŸ¦‹πŸŒΈ

πŸ“¦ where this page comes from