Provided by: libmojolicious-perl_9.35+dfsg-1_all bug

NAME

       Mojolicious::Guides::Testing - Web Application Testing Made Easy

OVERVIEW

       This document is an introduction to testing web applications with Test::Mojo. Test::Mojo can be thought
       of as a module that provides all of the tools and testing assertions needed to test web applications in a
       Perl-ish way.

       While Test::Mojo can be used to test any web application, it has shortcuts designed to make testing
       Mojolicious web applications easy and pain-free.

       Please refer to the Test::Mojo documentation for a complete reference to many of the ideas and syntax
       introduced in this document.

       A test file for a simple web application might look like:

         use Mojo::Base -strict;

         use Test::Mojo;
         use Test::More;

         # Start a Mojolicious app named "Celestial"
         my $t = Test::Mojo->new('Celestial');

         # Post a JSON document
         $t->post_ok('/notifications' => json => {event => 'full moon'})
           ->status_is(201)
           ->json_is('/message' => 'notification created');

         # Perform GET requests and look at the responses
         $t->get_ok('/sunrise')
           ->status_is(200)
           ->content_like(qr/ am$/);
         $t->get_ok('/sunset')
           ->status_is(200)
           ->content_like(qr/ pm$/);

         # Post a URL-encoded form
         $t->post_ok('/insurance' => form => {name => 'Jimmy', amount => '€3.000.000'})
           ->status_is(200);

         # Use Test::More's like() to check the response
         like $t->tx->res->dom->at('div#thanks')->text, qr/thank you/, 'thanks';

         done_testing();

       In the rest of this document we'll explore these concepts and others related to Test::Mojo.

CONCEPTS

       Essentials every Mojolicious developer should know.

   Test::Mojo at a glance
       The Test::More module bundled with Perl includes several primitive test assertions, such as "ok", "is",
       "isnt", "like", "unlike", "cmp_ok", etc. An assertion "passes" if its expression returns a true value.
       The assertion method prints "ok" or "not ok" if an assertion passes or fails (respectively).

       Test::Mojo supplies additional test assertions organized around the web application request/response
       transaction (transport, response headers, response bodies, etc.), and WebSocket communications.

       One interesting thing of note: the return value of Test::Mojo object assertions is always the test object
       itself, allowing us to "chain" test assertion methods. So rather than grouping related test statements
       like this:

         $t->get_ok('/frogs');
         $t->status_is(200);
         $t->content_like(qr/bullfrog/);
         $t->content_like(qr/hypnotoad/);

       Method chaining allows us to connect test assertions that belong together:

         $t->get_ok('/frogs')
           ->status_is(200)
           ->content_like(qr/bullfrog/)
           ->content_like(qr/hypnotoad/);

       This makes for a much more concise and coherent testing experience: concise because we are not repeating
       the invocant for each test, and coherent because assertions that belong to the same request are
       syntactically bound in the same method chain.

       Occasionally it makes sense to break up a test to perform more complex assertions on a response.
       Test::Mojo exposes the entire transaction object so you can get all the data you need from a response:

         $t->put_ok('/bees' => json => {type => 'worker', name => 'Karl'})
           ->status_is(202)
           ->json_has('/id');

         # Pull out the id from the response
         my $newbee = $t->tx->res->json('/id');

         # Make a new request with data from the previous response
         $t->get_ok("/bees/$newbee")
           ->status_is(200)
           ->json_is('/name' => 'Karl');

       The Test::Mojo object is stateful. As long as we haven't started a new transaction by invoking one of the
       *_ok methods, the request and response objects from the previous transaction are available in the
       Test::Mojo object:

         # First transaction
         $t->get_ok('/frogs?q=bullfrog' => {'Content-Type' => 'application/json'})
           ->status_is(200)
           ->json_like('/0/species' => qr/catesbeianus/i);

         # Still first transaction
         $t->content_type_is('application/json');

         # Second transaction
         $t->get_ok('/frogs?q=banjo' => {'Content-Type' => 'text/html'})
           ->status_is(200)
           ->content_like(qr/interioris/i);

         # Still second transaction
         $t->content_type_is('text/html');

       This statefulness also enables Test::Mojo to handle sessions, follow redirects, and inspect past
       responses during a redirect.

   The Test::Mojo object
       The Test::Mojo object manages the Mojolicious application lifecycle (if a Mojolicious application class
       is supplied) as well as exposes the built-in Mojo::UserAgent object. To create a bare Test::Mojo object:

         my $t = Test::Mojo->new;

       This object initializes a Mojo::UserAgent object and provides a variety of test assertion methods for
       accessing a web application. For example, with this object, we could test any running web application:

         $t->get_ok('https://www.google.com/')
           ->status_is(200)
           ->content_like(qr/search/i);

       You can access the user agent directly if you want to make web requests without triggering test
       assertions:

         my $tx = $t->ua->post('https://duckduckgo.com/html' => form => {q => 'hypnotoad'});
         $tx->result->dom->find('a.result__a')->each(sub { say $_->text });

       See Mojo::UserAgent for the complete API and return values.

   Testing Mojolicious applications
       If you pass the name of a Mojolicious application class (e.g., 'MyApp') to the Test::Mojo constructor,
       Test::Mojo will instantiate the class and start it, and cause it to listen on a random (unused) port
       number. Testing a Mojolicious application using Test::Mojo will never conflict with running applications,
       including the application you're testing.

       The Mojo::UserAgent object in Test::Mojo will know where the application is running and make requests to
       it. Once the tests have completed, the Mojolicious application will be torn down.

         # Listens on localhost:32114 (some unused TCP port)
         my $t = Test::Mojo->new('Frogs');

       To test a Mojolicious::Lite application, pass the file path to the application script to the constructor.

         # Load application script relative to the "t" directory
         use Mojo::File qw(curfile);
         my $t = Test::Mojo->new(curfile->dirname->sibling('myapp.pl'));

       The object initializes a Mojo::UserAgent object, loads the Mojolicious application, binds and listens on
       a free TCP port (e.g., 32114), and starts the application event loop. When the Test::Mojo object ($t)
       goes out of scope, the application is stopped.

       Relative URLs in the test object method assertions ("get_ok", "post_ok", etc.) will be sent to the
       Mojolicious application started by Test::Mojo:

         # Rewritten to "http://localhost:32114/frogs"
         $t->get_ok('/frogs');

       Test::Mojo has a lot of handy shortcuts built into it to make testing Mojolicious or Mojolicious::Lite
       applications enjoyable.

       An example

       Let's spin up a Mojolicious application using "mojo generate app MyApp". The "mojo" utility will create a
       working application and a "t" directory with a working test file:

         $ mojo generate app MyApp
         [mkdir] /my_app/script
         [write] /my_app/script/my_app
         [chmod] /my_app/script/my_app 744
         ...
         [mkdir] /my_app/t
         [write] /my_app/t/basic.t
         ...

       Let's run the tests (we'll create the "log" directory to quiet the application output):

         $ cd my_app
         $ mkdir log
         $ prove -lv t
         t/basic.t ..
         ok 1 - GET /
         ok 2 - 200 OK
         ok 3 - content is similar
         1..3
         ok
         All tests successful.
         Files=1, Tests=3,  0 wallclock secs ( 0.03 usr  0.01 sys +  0.33 cusr  0.07 csys =  0.44 CPU)
         Result: PASS

       The boilerplate test file looks like this:

         use Mojo::Base -strict;

         use Test::More;
         use Test::Mojo;

         my $t = Test::Mojo->new('MyApp');
         $t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);

         done_testing();

       Here we can see our application class name "MyApp" is passed to the Test::Mojo constructor. Under the
       hood, Test::Mojo creates a new Mojo::Server instance, loads "MyApp" (which we just created), and runs the
       application.  We write our tests with relative URLs because Test::Mojo takes care of getting the request
       to the running test application (since its port may change between runs).

       Testing with configuration data

       We can alter the behavior of our application using environment variables (such as "MOJO_MODE") and
       through configuration values. One nice feature of Test::Mojo is its ability to pass configuration values
       directly from its constructor.

       Let's modify our application and add a "feature flag" to enable a new feature when the "enable_weather"
       configuration value is set:

         # Load configuration from hash returned by "my_app.conf"
         my $config = $self->plugin('Config');

         # Normal route to controller
         $r->get('/')->to('example#welcome');

         # NEW: this route only exists if "enable_weather" is set in the configuration
         if ($config->{enable_weather}) {
           $r->get('/weather' => sub ($c) {
             $c->render(text => "It's hot! 🔥");
           });
         }

       To test this new feature, we don't even need to create a configuration file—we can simply pass the
       configuration data to the application directly via Test::Mojo's constructor:

         my $t = Test::Mojo->new(MyApp => {enable_weather => 1});
         $t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);
         $t->get_ok('/weather')->status_is(200)->content_like(qr/🔥/);

       When we run these tests, Test::Mojo will pass this configuration data to the application, which will
       cause it to create a special "/weather" route that we can access in our tests. Unless "enable_weather" is
       set in a configuration file, this route will not exist when the application runs. Feature flags like this
       allow us to do soft rollouts of features, targeting a small audience for a period of time. Once the
       feature has been proven, we can refactor the conditional and make it a full release.

       This example shows how easy it is to start testing a Mojolicious application and how to set specific
       application configuration directives from a test file.

       Testing application helpers

       Let's say we register a helper in our application to generate an HTTP Basic Authorization header:

         use Mojo::Util qw(b64_encode);

         app->helper(basic_auth => sub ($c, @values) {
           return {Authorization => 'Basic ' . b64_encode join(':' => @values), ''};
         });

       How do we test application helpers like this? Test::Mojo has access to the application object, which
       allows us to invoke helpers from our test file:

         my $t = Test::Mojo->new('MyApp');

         is_deeply $t->app->basic_auth(bif => "Bif's Passwerdd"), {Authorization => 'Basic YmlmOkJpZidzIFBhc3N3ZXJkZA=='},
           'correct header value';

       Any aspect of the application (helpers, plugins, routes, etc.) can be introspected from Test::Mojo
       through the application object. This enables us to get deep test coverage of Mojolicious-based
       applications.

ASSERTIONS

       This section describes the basic test assertions supplied by Test::Mojo. There are four broad categories
       of assertions for HTTP requests:

       • HTTP requests

       • HTTP response status

       • HTTP response headers

       • HTTP response content/body

       WebSocket test assertions are covered in "Testing WebSocket web services".

   HTTP request assertions
       Test::Mojo has a Mojo::UserAgent object that allows it to make HTTP requests and check for HTTP transport
       errors.   HTTP  request assertions include "get_ok", "post_ok", etc. These assertions do not test whether
       the request was handled successfully, only that the web  application  handled  the  request  in  an  HTTP
       compliant way.

       You  may  also make HTTP requests using custom verbs (beyond "GET", "POST", "PUT", etc.) by building your
       own transaction object. See "Custom transactions" below.

       Using HTTP request assertions

       To post a URL-encoded form to the "/calls" endpoint of an application, we simply use the  "form"  content
       type shortcut:

         $t->post_ok('/calls' => form => {to => '+43.55.555.5555'});

       Which will create the following HTTP request:

         POST /calls HTTP/1.1
         Content-Length: 20
         Content-Type: application/x-www-form-urlencoded

         to=%2B43.55.555.5555

       The  *_ok HTTP request assertion methods accept the same arguments as their corresponding Mojo::UserAgent
       methods (except for the callback argument). This allows us to set headers and  build  query  strings  for
       authentic test situations:

         $t->get_ok('/internal/personnel' => {Authorization => 'Token secret-password'} => form => {q => 'Professor Plum'});

       which generates the following request:

         GET /internal/personnel?q=Professor+Plum HTTP/1.1
         Content-Length: 0
         Authorization: Token secret-password

       The  "form"  content  generator  (see Mojo::UserAgent::Transactor) will generate a query string for "GET"
       requests and "application/x-www-form-urlencoded" or "multipart/form-data" for POST requests.

       While these *_ok assertions make the HTTP requests we expect, they tell us  little  about  how  well  the
       application  handled  the  request.  The  application we're testing might have returned any content-type,
       body, or HTTP status code (200, 302, 400, 404, 500, etc.) and we wouldn't know it.

       Test::Mojo provides assertions to test almost every aspect of  the  HTTP  response,  including  the  HTTP
       response  status  code,  the  value  of  the  "Content-Type"  header,  and  other  arbitrary  HTTP header
       information.

   HTTP response status code
       While not technically an HTTP header, the status line is the first  line  in  an  HTTP  response  and  is
       followed  by the response headers. Testing the response status code is common in REST-based and other web
       applications that use the HTTP status codes to broadly indicate  the  type  of  response  the  server  is
       returning.

       Testing the status code is as simple as adding the "status_is" assertion:

         $t->post_ok('/doorbell' => form => {action => 'ring once'})
           ->status_is(200);

       Along  with  "status_isnt",  this  will cover most needs. For more elaborate status code testing, you can
       access the response internals directly:

         $t->post_ok('/doorbell' => form => {action => 'ring once'});
         is $t->tx->res->message, 'Moved Permanently', 'try next door';

   HTTP response headers
       Test::Mojo allows us to inspect and make assertions  about  HTTP  response  headers.  The  "Content-Type"
       header is commonly tested and has its own assertion:

         $t->get_ok('/map-of-the-world.pdf')
           ->content_type_is('application/pdf');

       This is equivalent to the more verbose:

         $t->get_ok('/map-of-the-world.pdf')
           ->header_is('Content-Type' => 'application/pdf');

       We can test for multiple headers in a single response using method chains:

         $t->get_ok('/map-of-the-world.pdf')
           ->content_type_is('application/pdf')
           ->header_isnt('Compression' => 'gzip')
           ->header_unlike('Server' => qr/IIS/i);

   HTTP response content assertions
       Test::Mojo also exposes a rich set of assertions for testing the body of a response, whether that body be
       HTML,  plain-text,  or  JSON.  The "content_*" methods look at the body of the response as plain text (as
       defined by the response's character set):

         $t->get_ok('/scary-things/spiders.json')
           ->content_is('{"arachnid":"brown recluse"}');

       Although this is a JSON document, "content_is" treats it as if it were  a  text  document.  This  may  be
       useful for situations where we're looking for a particular string and not concerned with the structure of
       the document. For example, we can do the same thing with an HTML document:

         $t->get_ok('/scary-things/spiders.html')
           ->content_like(qr{<title>All The Spiders</title>});

       But  because  Test::Mojo  has  access  to  everything  that  Mojo::UserAgent does, we can introspect JSON
       documents as well as DOM-based documents (HTML, XML) with assertions that  allow  us  to  check  for  the
       existence of elements as well as inspect the content of text nodes.

       JSON response assertions

       Test::Mojo's  Mojo::UserAgent  has  access  to  a  JSON  parser, which allows us to test to see if a JSON
       response contains a value at a location in the document using JSON pointer syntax:

         $t->get_ok('/animals/friendly.json')
           ->json_has('/beings/jeremiah/age');

       This assertion tells us that the "friendly.json" document contains a value at the  "/beings/jeremiah/age"
       JSON pointer location. We can also inspect the value at JSON pointer locations:

         $t->get_ok('/animals/friendly.json')
           ->json_has('/beings/jeremiah/age')
           ->json_is('/beings/jeremiah/age' => 42)
           ->json_like('/beings/jeremiah/species' => qr/bullfrog/i);

       JSON pointer syntax makes testing JSON responses simple and readable.

       DOM response assertions

       We  can  also inspect HTML and XML responses using the Mojo::DOM parser in the user agent. Here are a few
       examples from the Test::Mojo documentation:

         $t->text_is('div.foo[x=y]' => 'Hello!');
         $t->text_is('html head title' => 'Hello!', 'right title');

       The Mojo::DOM parser uses the CSS selector syntax described in Mojo::DOM::CSS, allowing us  to  test  for
       values  in  HTML  and  XML  documents without resorting to typically verbose and inflexible DOM traversal
       methods.

ADVANCED TOPICS

       This section describes some complex (but common) testing situations  that  Test::Mojo  excels  in  making
       simple.

   Redirects
       The  Mojo::UserAgent  object  in Test::Mojo can handle HTTP redirections internally to whatever level you
       need.  Let's say we have a web service that  redirects  "/1"  to  "/2",  "/2"  redirects  to  "/3",  "/3"
       redirects to "/4", and "/4" redirects to "/5":

         GET /1

       returns:

         302 Found
         Location: /2

       and:

         GET /2

       returns:

         302 Found
         Location: /3

       and so forth, up to "/5":

         GET /5

       which returns the data we wanted:

         200 OK

         {"message":"this is five"}

       We  can  tell  the  user agent in Test::Mojo how to deal with redirects. Each test is making a request to
       "GET /1", but we vary the number of redirects the user agent should follow with each test:

         my $t = Test::Mojo->new;

         $t->get_ok('/1')
           ->header_is(location => '/2');

         $t->ua->max_redirects(1);
         $t->get_ok('/1')
           ->header_is(location => '/3');

         $t->ua->max_redirects(2);
         $t->get_ok('/1')
           ->header_is(location => '/4');

         # Look at the previous hop
         is $t->tx->previous->res->headers->location, '/3', 'previous redirect';

         $t->ua->max_redirects(3);
         $t->get_ok('/1')
           ->header_is(location => '/5');

         $t->ua->max_redirects(4);
         $t->get_ok('/1')
           ->json_is('/message' => 'this is five');

       When we set "max_redirects", it stays set for the life of the test object until we change it.

       Test::Mojo's handling of HTTP redirects eliminates the need for making many, sometimes an unknown number,
       of redirections to keep testing precise and easy to follow (ahem).

   Cookies and session management
       We can use Test::Mojo to  test  applications  that  keep  session  state  in  cookies.  By  default,  the
       Mojo::UserAgent  object  in  Test::Mojo  will  manage  session  for  us  by  saving  and  sending cookies
       automatically, just like common web browsers:

         use Mojo::Base -strict;

         use Test::More;
         use Test::Mojo;

         my $t = Test::Mojo->new('MyApp');

         # No authorization cookie
         $t->get_ok('/')
           ->status_is(401)
           ->content_is('Please log in');

         # Application sets an authorization cookie
         $t->post_ok('/login' => form => {password => 'let me in'})
           ->status_is(200)
           ->content_is('You are logged in');

         # Sends the cookie from the previous transaction
         $t->get_ok('/')
           ->status_is(200)
           ->content_like(qr/You logged in at \d+/);

         # Clear the cookies
         $t->reset_session;

         # No authorization cookie again
         $t->get_ok('/')
           ->status_is(401)
           ->content_is('Please log in');

       We can also  inspect  cookies  in  responses  for  special  values  through  the  transaction's  response
       (Mojo::Message::Response) object:

         $t->get_ok('/');
         like $t->tx->res->cookie('smarty'), qr/smarty=pants/, 'cookie found';

   Custom transactions
       Let's  say we have an application that responds to a new HTTP verb "RING" and to use it we must also pass
       in a secret  cookie  value.  This  is  not  a  problem.  We  can  test  the  application  by  creating  a
       Mojo::Transaction  object,  setting the cookie (see Mojo::Message::Request), then passing the transaction
       object to "request_ok":

         # Use custom "RING" verb
         my $tx = $t->ua->build_tx(RING => '/doorbell');

         # Set a special cookie
         $tx->req->cookies({name => 'Secret', value => "don't tell anybody"});

         # Make the request
         $t->request_ok($tx)
           ->status_is(200)
           ->json_is('/status' => 'ding dong');

   Testing WebSocket web services
       While the message flow on WebSocket connections can be rather dynamic, it more often than  not  is  quite
       predictable, which allows this rather pleasant Test::Mojo WebSocket API to be used:

         use Mojo::Base -strict;

         use Test::More;
         use Test::Mojo;

         # Test echo web service
         my $t = Test::Mojo->new('EchoService');
         $t->websocket_ok('/echo')
           ->send_ok('Hello Mojo!')
           ->message_ok
           ->message_is('echo: Hello Mojo!')
           ->finish_ok;

         # Test JSON web service
         $t->websocket_ok('/echo.json')
           ->send_ok({json => {test => [1, 2, 3]}})
           ->message_ok
           ->json_message_is('/test' => [1, 2, 3])
           ->finish_ok;

         done_testing();

       Because  of  their  inherent  asynchronous  nature,  testing  WebSocket communications can be tricky. The
       Test::Mojo WebSocket assertions serialize messages via event loop primitives. This enables  us  to  treat
       WebSocket messages as if they were using the same request-response communication pattern we're accustomed
       to with HTTP.

       To  illustrate, let's walk through these tests. In the first test, we use the "websocket_ok" assertion to
       ensure that we can connect to our application's WebSocket route  at  "/echo"  and  that  it's  "speaking"
       WebSocket protocol to us. The next "send_ok" assertion tests the connection again (in case it closed, for
       example)  and attempts to send the message "Hello Mojo!". The next assertion, "message_ok", blocks (using
       the Mojo::IOLoop singleton in the application) and waits for a response from the server. The response  is
       then  compared  with 'echo: Hello Mojo!' in the "message_is" assertion, and finally we close and test our
       connection status again with "finish_ok".

       The second test is like the first, but now we're sending and expecting JSON documents at "/echo.json". In
       the  "send_ok"  assertion  we  take  advantage  of  Mojo::UserAgent's   JSON   content   generator   (see
       Mojo::UserAgent::Transactor) to marshal hash and array references into JSON documents, and then send them
       as  a  WebSocket  message. We wait (block) for a response from the server with "message_ok". Then because
       we're expecting a JSON document back, we  can  leverage  "json_message_ok"  which  parses  the  WebSocket
       response  body and returns an object we can access through Mojo::JSON::Pointer syntax. Then we close (and
       test) our WebSocket connection.

       Testing WebSocket servers does not get any simpler than with Test::Mojo.

   Extending Test::Mojo
       If you see that you're writing a lot of test assertions that  aren't  chainable,  you  may  benefit  from
       writing your own test assertions. Let's say we want to test the "Location" header after a redirect. We'll
       create a new class with Role::Tiny that implements a test assertion named "location_is":

         package Test::Mojo::Role::Location;
         use Mojo::Base -role, -signatures;

         sub location_is ($self, $value, $desc = "Location: $value") {
           return $self->test('is', $self->tx->res->headers->location, $value, $desc);
         }

         1;

       When  we  make  new  test  assertions using roles, we want to use method signatures that match other *_is
       methods in Test::Mojo, so here we accept  the  test  object,  the  value  to  compare,  and  an  optional
       description.

       We  assign  a default description value ($desc), then we use "test" in Test::Mojo to compare the location
       header with the expected header value, and finally propagates the Test::Mojo object for method chaining.

       With this new package, we're ready to compose a new test object that uses the role:

         my $t = Test::Mojo->with_roles('+Location')->new('MyApp');

         $t->post_ok('/redirect/mojo' => json => {message => 'Mojo, here I come!'})
           ->status_is(302)
           ->location_is('http://mojolicious.org')
           ->or(sub { diag 'I miss tempire.' });

       In this section we've covered how to add custom test assertions to Test::Mojo with roles and how  to  use
       those roles to simplify testing.

MORE

       You   can   continue   with   Mojolicious::Guides   now   or   take   a  look  at  the  Mojolicious  wiki
       <https://github.com/mojolicious/mojo/wiki>, which contains a lot more documentation and examples by  many
       different authors.

SUPPORT

       If  you  have  any  questions  the documentation might not yet answer, don't hesitate to ask in the Forum
       <https://forum.mojolicious.org>,    on    Matrix    <https://matrix.to/#/#mojo:matrix.org>,    or     IRC
       <https://web.libera.chat/#mojo>.

perl v5.36.0                                       2023-10-28                  Mojolicious::Guides::Testing(3pm)