.\" Automatically generated by Pod::Man 4.10 (Pod::Simple 3.35) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" Set up some character translations and predefined strings. \*(-- will .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left .\" double quote, and \*(R" will give a right double quote. \*(C+ will .\" give a nicer C++. Capital omega is used to do unbreakable dashes and .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, .\" nothing in troff, for use with C<>. .tr \(*W- .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' .ie n \{\ . ds -- \(*W- . ds PI pi . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch . ds L" "" . ds R" "" . ds C` "" . ds C' "" 'br\} .el\{\ . ds -- \|\(em\| . ds PI \(*p . ds L" `` . ds R" '' . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" ======================================================================== .\" .IX Title "Mojolicious::Guides::Testing 3pm" .TH Mojolicious::Guides::Testing 3pm "2019-02-05" "perl v5.28.1" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH "NAME" Mojolicious::Guides::Testing \- Web Application Testing Made Easy .SH "OVERVIEW" .IX Header "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. .PP 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. .PP Please refer to the Test::Mojo documentation for a complete reference to many of the ideas and syntax introduced in this document. .PP A test file for a simple web application might look like: .PP .Vb 1 \& use Mojo::Base \-strict; \& \& use Test::Mojo; \& use Test::More; \& \& # Start a Mojolicious app named "Celestial" \& my $t = Test::Mojo\->new(\*(AqCelestial\*(Aq); \& \& # Post a JSON document \& $t\->post_ok(\*(Aq/notifications\*(Aq => json => {event => \*(Aqfull moon\*(Aq}) \& \->status_is(201) \& \->json_is(\*(Aq/message\*(Aq => \*(Aqnotification created\*(Aq); \& \& # Perform GET requests and look at the responses \& $t\->get_ok(\*(Aq/sunrise\*(Aq) \& \->status_is(200) \& \->content_like(qr/ am$/); \& $t\->get_ok(\*(Aq/sunset\*(Aq) \& \->status_is(200) \& \->content_like(qr/ pm$/); \& \& # Post a URL\-encoded form \& $t\->post_ok(\*(Aq/insurance\*(Aq => form => {name => \*(AqJimmy\*(Aq, amount => \*(Aq€3.000.000\*(Aq}) \& \->status_is(200); \& \& # Use Test::More\*(Aqs like() to check the response \& like $t\->tx\->res\->dom\->at(\*(Aqdiv#thanks\*(Aq)\->text, qr/thank you/, \*(Aqthanks\*(Aq; \& \& done_testing(); .Ve .PP In the rest of this document we'll explore these concepts and others related to Test::Mojo. .SH "CONCEPTS" .IX Header "CONCEPTS" Essentials every Mojolicious developer should know. .SS "Test::Mojo at a glance" .IX Subsection "Test::Mojo at a glance" The Test::More module bundled with Perl includes several primitive test assertions, such as \f(CW\*(C`ok\*(C'\fR, \f(CW\*(C`is\*(C'\fR, \f(CW\*(C`isnt\*(C'\fR, \f(CW\*(C`like\*(C'\fR, \f(CW\*(C`unlike\*(C'\fR, \f(CW\*(C`cmp_ok\*(C'\fR, etc. An assertion \*(L"passes\*(R" if its expression returns a true value. The assertion method prints \*(L"ok\*(R" or \*(L"not ok\*(R" if an assertion passes or fails (respectively). .PP Test::Mojo supplies additional test assertions organized around the web application request/response transaction (transport, response headers, response bodies, etc.), and WebSocket communications. .PP One interesting thing of note: the return value of Test::Mojo object assertions is always the test object itself, allowing us to \*(L"chain\*(R" test assertion methods. So rather than grouping related test statements like this: .PP .Vb 4 \& $t\->get_ok(\*(Aq/frogs\*(Aq); \& $t\->status_is(200); \& $t\->content_like(qr/bullfrog/); \& $t\->content_like(qr/hypnotoad/); .Ve .PP Method chaining allows us to connect test assertions that belong together: .PP .Vb 4 \& $t\->get_ok(\*(Aq/frogs\*(Aq) \& \->status_is(200) \& \->content_like(qr/bullfrog/) \& \->content_like(qr/hypnotoad/); .Ve .PP This makes for a much more \fIconcise\fR and \fIcoherent\fR 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. .PP 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: .PP .Vb 3 \& $t\->put_ok(\*(Aq/bees\*(Aq => json => {type => \*(Aqworker\*(Aq, name => \*(AqKarl\*(Aq}) \& \->status_is(202) \& \->json_has(\*(Aq/id\*(Aq); \& \& # Pull out the id from the response \& my $newbee = $t\->tx\->res\->json(\*(Aq/id\*(Aq); \& \& # Make a new request with data from the previous response \& $t\->get_ok("/bees/$newbee") \& \->status_is(200) \& \->json_is(\*(Aq/name\*(Aq => \*(AqKarl\*(Aq); .Ve .PP The Test::Mojo object is \fIstateful\fR. As long as we haven't started a new transaction by invoking one of the \f(CW*_ok\fR methods, the request and response objects from the previous transaction are available in the Test::Mojo object: .PP .Vb 4 \& # First transaction \& $t\->get_ok(\*(Aq/frogs?q=bullfrog\*(Aq => {\*(AqContent\-Type\*(Aq => \*(Aqapplication/json\*(Aq}) \& \->status_is(200) \& \->json_like(\*(Aq/0/species\*(Aq => qr/catesbeianus/i); \& \& # Still first transaction \& $t\->content_type_is(\*(Aqapplication/json\*(Aq); \& \& # Second transaction \& $t\->get_ok(\*(Aq/frogs?q=banjo\*(Aq => {\*(AqContent\-Type\*(Aq => \*(Aqtext/html\*(Aq}) \& \->status_is(200) \& \->content_like(qr/interioris/i); \& \& # Still second transaction \& $t\->content_type_is(\*(Aqtext/html\*(Aq); .Ve .PP This statefulness also enables Test::Mojo to handle sessions, follow redirects, and inspect past responses during a redirect. .SS "The Test::Mojo object" .IX Subsection "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: .PP .Vb 1 \& my $t = Test::Mojo\->new; .Ve .PP 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: .PP .Vb 3 \& $t\->get_ok(\*(Aqhttps://www.google.com/\*(Aq) \& \->status_is(200) \& \->content_like(qr/search/i); .Ve .PP You can access the user agent directly if you want to make web requests without triggering test assertions: .PP .Vb 3 \& my $tx = $t\->ua\->post( \& \*(Aqhttps://duckduckgo.com/html\*(Aq => form => {q => \*(Aqhypnotoad\*(Aq}); \& $tx\->result\->dom\->find(\*(Aqa.result_\|_a\*(Aq)\->each(sub { say $_\->text }); .Ve .PP See Mojo::UserAgent for the complete \s-1API\s0 and return values. .SS "Testing Mojolicious applications" .IX Subsection "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. .PP 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. .PP .Vb 2 \& # Listens on localhost:32114 (some unused TCP port) \& my $t = Test::Mojo\->new(\*(AqFrogs\*(Aq); .Ve .PP This object initializes a Mojo::UserAgent object, loads the Mojolicious application \f(CW\*(C`Frogs\*(C'\fR, binds and listens on a free \s-1TCP\s0 port (e.g., 32114), and starts the application event loop. When the Test::Mojo object (\f(CW$t\fR) goes out of scope, the application is stopped. .PP Relative URLs in the test object method assertions (\f(CW\*(C`get_ok\*(C'\fR, \f(CW\*(C`post_ok\*(C'\fR, etc.) will be sent to the Mojolicious application started by Test::Mojo: .PP .Vb 2 \& # Rewritten to "http://localhost:32114/frogs" \& $t\->get_ok(\*(Aq/frogs\*(Aq); .Ve .PP Test::Mojo has a lot of handy shortcuts built into it to make testing Mojolicious or Mojolicious::Lite applications enjoyable. .PP \fIAn example\fR .IX Subsection "An example" .PP Let's spin up a Mojolicious application using \f(CW\*(C`mojo generate app MyApp\*(C'\fR. The \&\f(CW\*(C`mojo\*(C'\fR utility will create a working application and a \f(CW\*(C`t\*(C'\fR directory with a working test file: .PP .Vb 8 \& $ 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 \& ... .Ve .PP Let's run the tests (we'll create the \f(CW\*(C`log\*(C'\fR directory to quiet the application output): .PP .Vb 10 \& $ 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 .Ve .PP The boilerplate test file looks like this: .PP .Vb 1 \& use Mojo::Base \-strict; \& \& use Test::More; \& use Test::Mojo; \& \& my $t = Test::Mojo\->new(\*(AqMyApp\*(Aq); \& $t\->get_ok(\*(Aq/\*(Aq)\->status_is(200)\->content_like(qr/Mojolicious/i); \& \& done_testing(); .Ve .PP Here we can see our application class name \f(CW\*(C`MyApp\*(C'\fR is passed to the Test::Mojo constructor. Under the hood, Test::Mojo creates a new Mojo::Server instance, loads \f(CW\*(C`MyApp\*(C'\fR (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). .PP \fITesting with configuration data\fR .IX Subsection "Testing with configuration data" .PP We can alter the behavior of our application using environment variables (such as \f(CW\*(C`MOJO_MODE\*(C'\fR) and through configuration values. One nice feature of Test::Mojo is its ability to pass configuration values directly from its constructor. .PP Let's modify our application and add a \*(L"feature flag\*(R" to enable a new feature when the \f(CW\*(C`enable_weather\*(C'\fR configuration value is set: .PP .Vb 2 \& # Load configuration from hash returned by "my_app.conf" \& my $config = $self\->plugin(\*(AqConfig\*(Aq); \& \& # Normal route to controller \& $r\->get(\*(Aq/\*(Aq)\->to(\*(Aqexample#welcome\*(Aq); \& \& # NEW: this route only exists if "enable_weather" is set in the configuration \& if ($config\->{enable_weather}) { \& $r\->get(\*(Aq/weather\*(Aq => sub { shift\->render(text => "It\*(Aqs hot! 🔥") } \& } .Ve .PP 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: .PP .Vb 3 \& my $t = Test::Mojo\->new(MyApp => {enable_weather => 1}); \& $t\->get_ok(\*(Aq/\*(Aq)\->status_is(200)\->content_like(qr/Mojolicious/i); \& $t\->get_ok(\*(Aq/weather\*(Aq)\->status_is(200)\->content_like(qr/🔥/); .Ve .PP When we run these tests, Test::Mojo will pass this configuration data to the application, which will cause it to create a special \f(CW\*(C`/weather\*(C'\fR route that we can access in our tests. Unless \f(CW\*(C`enable_weather\*(C'\fR 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. .PP 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. .PP \fITesting application helpers\fR .IX Subsection "Testing application helpers" .PP Let's say we register a helper in our application to generate an \s-1HTTP\s0 Basic Authorization header: .PP .Vb 1 \& use Mojo::Util \*(Aqb64_encode\*(Aq; \& \& app\->helper(basic_auth => sub { \& my ($c, @values) = @_; \& return {Authorization => \*(AqBasic \*(Aq . b64_encode join(\*(Aq:\*(Aq => @values), \*(Aq\*(Aq}; \& }); .Ve .PP 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: .PP .Vb 1 \& my $t = Test::Mojo\->new(\*(AqMyApp\*(Aq); \& \& is_deeply $t\->app\->basic_auth(bif => "Bif\*(Aqs Passwerdd"), \& {Authorization => \*(AqBasic YmlmOkJpZidzIFBhc3N3ZXJkZA==\*(Aq}, \& \*(Aqcorrect header value\*(Aq; .Ve .PP 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. .SH "ASSERTIONS" .IX Header "ASSERTIONS" This section describes the basic test assertions supplied by Test::Mojo. There are four broad categories of assertions for \s-1HTTP\s0 requests: .IP "\(bu" 2 \&\s-1HTTP\s0 requests .IP "\(bu" 2 \&\s-1HTTP\s0 response status .IP "\(bu" 2 \&\s-1HTTP\s0 response headers .IP "\(bu" 2 \&\s-1HTTP\s0 response content/body .PP WebSocket test assertions are covered in \*(L"Testing WebSocket web services\*(R". .SS "\s-1HTTP\s0 request assertions" .IX Subsection "HTTP request assertions" Test::Mojo has a Mojo::UserAgent object that allows it to make \s-1HTTP\s0 requests and check for \s-1HTTP\s0 transport errors. \s-1HTTP\s0 request assertions include \&\f(CW\*(C`get_ok\*(C'\fR, \f(CW\*(C`post_ok\*(C'\fR, etc. These assertions do not test whether the request was handled \fIsuccessfully\fR, only that the web application handled the request in an \s-1HTTP\s0 compliant way. .PP You may also make \s-1HTTP\s0 requests using custom verbs (beyond \f(CW\*(C`GET\*(C'\fR, \f(CW\*(C`POST\*(C'\fR, \&\f(CW\*(C`PUT\*(C'\fR, etc.) by building your own transaction object. See \&\*(L"Custom transactions\*(R" below. .PP \fIUsing \s-1HTTP\s0 request assertions\fR .IX Subsection "Using HTTP request assertions" .PP To post a URL-encoded form to the \f(CW\*(C`/calls\*(C'\fR endpoint of an application, we simply use the \f(CW\*(C`form\*(C'\fR content type shortcut: .PP .Vb 1 \& $t\->post_ok(\*(Aq/calls\*(Aq => form => {to => \*(Aq+43.55.555.5555\*(Aq}); .Ve .PP Which will create the following \s-1HTTP\s0 request: .PP .Vb 3 \& POST /calls HTTP/1.1 \& Content\-Length: 20 \& Content\-Type: application/x\-www\-form\-urlencoded \& \& to=%2B43.55.555.5555 .Ve .PP The \f(CW*_ok\fR \s-1HTTP\s0 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: .PP .Vb 2 \& $t\->get_ok(\*(Aq/internal/personnel\*(Aq => {Authorization => \*(AqToken secret\-password\*(Aq} \& => form => {q => \*(AqProfessor Plum\*(Aq}); .Ve .PP which generates the following request: .PP .Vb 3 \& GET /internal/personnel?q=Professor+Plum HTTP/1.1 \& Content\-Length: 0 \& Authorization: Token secret\-password .Ve .PP The \f(CW\*(C`form\*(C'\fR content generator (see Mojo::UserAgent::Transactor) will generate a query string for \f(CW\*(C`GET\*(C'\fR requests and \f(CW\*(C`application/x\-www\-form\-urlencoded\*(C'\fR or \&\f(CW\*(C`multipart/form\-data\*(C'\fR for \s-1POST\s0 requests. .PP While these \f(CW*_ok\fR assertions make the \s-1HTTP\s0 \fIrequests\fR we expect, they tell us little about \fIhow well\fR the application handled the request. The application we're testing might have returned any content-type, body, or \s-1HTTP\s0 status code (200, 302, 400, 404, 500, etc.) and we wouldn't know it. .PP Test::Mojo provides assertions to test almost every aspect of the \s-1HTTP\s0 response, including the \s-1HTTP\s0 response status code, the value of the \&\f(CW\*(C`Content\-Type\*(C'\fR header, and other arbitrary \s-1HTTP\s0 header information. .SS "\s-1HTTP\s0 response status code" .IX Subsection "HTTP response status code" While not technically an \s-1HTTP\s0 header, the status line is the first line in an \&\s-1HTTP\s0 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 \s-1HTTP\s0 status codes to broadly indicate the type of response the server is returning. .PP Testing the status code is as simple as adding the \f(CW\*(C`status_is\*(C'\fR assertion: .PP .Vb 2 \& $t\->post_ok(\*(Aq/doorbell\*(Aq => form => {action => \*(Aqring once\*(Aq}) \& \->status_is(200); .Ve .PP Along with \f(CW\*(C`status_isnt\*(C'\fR, this will cover most needs. For more elaborate status code testing, you can access the response internals directly: .PP .Vb 2 \& $t\->post_ok(\*(Aq/doorbell\*(Aq => form => {action => \*(Aqring once\*(Aq}); \& is $t\->tx\->res\->message, \*(AqMoved Permanently\*(Aq, \*(Aqtry next door\*(Aq; .Ve .SS "\s-1HTTP\s0 response headers" .IX Subsection "HTTP response headers" Test::Mojo allows us to inspect and make assertions about \s-1HTTP\s0 response headers. The \f(CW\*(C`Content\-Type\*(C'\fR header is commonly tested and has its own assertion: .PP .Vb 2 \& $t\->get_ok(\*(Aq/map\-of\-the\-world.pdf\*(Aq) \& \->content_type_is(\*(Aqapplication/pdf\*(Aq); .Ve .PP This is equivalent to the more verbose: .PP .Vb 2 \& $t\->get_ok(\*(Aq/map\-of\-the\-world.pdf\*(Aq) \& \->header_is(\*(AqContent\-Type\*(Aq => \*(Aqapplication/pdf\*(Aq); .Ve .PP We can test for multiple headers in a single response using method chains: .PP .Vb 4 \& $t\->get_ok(\*(Aq/map\-of\-the\-world.pdf\*(Aq) \& \->content_type_is(\*(Aqapplication/pdf\*(Aq) \& \->header_isnt(\*(AqCompression\*(Aq => \*(Aqgzip\*(Aq) \& \->header_unlike(\*(AqServer\*(Aq => qr/IIS/i); .Ve .SS "\s-1HTTP\s0 response content assertions" .IX Subsection "HTTP response content assertions" Test::Mojo also exposes a rich set of assertions for testing the body of a response, whether that body be \s-1HTML,\s0 plain-text, or \s-1JSON.\s0 The \f(CW\*(C`content_*\*(C'\fR methods look at the body of the response as plain text (as defined by the response's character set): .PP .Vb 2 \& $t\->get_ok(\*(Aq/scary\-things/spiders.json\*(Aq) \& \->content_is(\*(Aq{"arachnid":"brown recluse"}\*(Aq); .Ve .PP Although this is a \s-1JSON\s0 document, \f(CW\*(C`content_is\*(C'\fR 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 \s-1HTML\s0 document: .PP .Vb 2 \& $t\->get_ok(\*(Aq/scary\-things/spiders.html\*(Aq) \& \->content_like(qr{