package Dancer2::Manual::Testing; # ABSTRACT: Writing tests for Dancer2 use strict; use warnings; 1; __END__ =pod =encoding UTF-8 =head1 NAME Dancer2::Manual::Testing - Writing tests for Dancer2 =head1 VERSION version 2.1.0 =head1 Basic application testing Since L produces PSGI applications, you can easily write tests using L and provide your Dancer application as the app for testing. A basic test (which we also scaffold with L) 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 L and L to see the different options for sending parameters. =head3 Subtests Tests can be separated using L's C 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(); =head1 Cookies To handle cookies, which are mostly used for maintaining sessions, the following modules can be used: =over 4 =item * L =item * L =item * L =back Taking the previous test, assuming it actually creates and uses cookies for sessions (see L and perhaps L 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 L 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 L to work. =head3 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"; # ... =head1 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' ); =head1 AUTHOR Dancer Core Developers =head1 COPYRIGHT AND LICENSE 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. =cut