1Mojolicious::Guides::TeUssteirngC(o3n)tributed Perl DocuMmoejnotlaitciioonus::Guides::Testing(3)
2
3
4

NAME

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

OVERVIEW

9       This document is an introduction to testing web applications with
10       Test::Mojo. Test::Mojo can be thought of as a module that provides all
11       of the tools and testing assertions needed to test web applications in
12       a Perl-ish way.
13
14       While Test::Mojo can be used to test any web application, it has
15       shortcuts designed to make testing Mojolicious web applications easy
16       and pain-free.
17
18       Please refer to the Test::Mojo documentation for a complete reference
19       to many of the ideas and syntax introduced in this document.
20
21       A test file for a simple web application might look like:
22
23         use Mojo::Base -strict;
24
25         use Test::Mojo;
26         use Test::More;
27
28         # Start a Mojolicious app named "Celestial"
29         my $t = Test::Mojo->new('Celestial');
30
31         # Post a JSON document
32         $t->post_ok('/notifications' => json => {event => 'full moon'})
33           ->status_is(201)
34           ->json_is('/message' => 'notification created');
35
36         # Perform GET requests and look at the responses
37         $t->get_ok('/sunrise')
38           ->status_is(200)
39           ->content_like(qr/ am$/);
40         $t->get_ok('/sunset')
41           ->status_is(200)
42           ->content_like(qr/ pm$/);
43
44         # Post a URL-encoded form
45         $t->post_ok('/insurance' => form => {name => 'Jimmy', amount => '€3.000.000'})
46           ->status_is(200);
47
48         # Use Test::More's like() to check the response
49         like $t->tx->res->dom->at('div#thanks')->text, qr/thank you/, 'thanks';
50
51         done_testing();
52
53       In the rest of this document we'll explore these concepts and others
54       related to Test::Mojo.
55

CONCEPTS

57       Essentials every Mojolicious developer should know.
58
59   Test::Mojo at a glance
60       The Test::More module bundled with Perl includes several primitive test
61       assertions, such as "ok", "is", "isnt", "like", "unlike", "cmp_ok",
62       etc. An assertion "passes" if its expression returns a true value. The
63       assertion method prints "ok" or "not ok" if an assertion passes or
64       fails (respectively).
65
66       Test::Mojo supplies additional test assertions organized around the web
67       application request/response transaction (transport, response headers,
68       response bodies, etc.), and WebSocket communications.
69
70       One interesting thing of note: the return value of Test::Mojo object
71       assertions is always the test object itself, allowing us to "chain"
72       test assertion methods. So rather than grouping related test statements
73       like this:
74
75         $t->get_ok('/frogs');
76         $t->status_is(200);
77         $t->content_like(qr/bullfrog/);
78         $t->content_like(qr/hypnotoad/);
79
80       Method chaining allows us to connect test assertions that belong
81       together:
82
83         $t->get_ok('/frogs')
84           ->status_is(200)
85           ->content_like(qr/bullfrog/)
86           ->content_like(qr/hypnotoad/);
87
88       This makes for a much more concise and coherent testing experience:
89       concise because we are not repeating the invocant for each test, and
90       coherent because assertions that belong to the same request are
91       syntactically bound in the same method chain.
92
93       Occasionally it makes sense to break up a test to perform more complex
94       assertions on a response. Test::Mojo exposes the entire transaction
95       object so you can get all the data you need from a response:
96
97         $t->put_ok('/bees' => json => {type => 'worker', name => 'Karl'})
98           ->status_is(202)
99           ->json_has('/id');
100
101         # Pull out the id from the response
102         my $newbee = $t->tx->res->json('/id');
103
104         # Make a new request with data from the previous response
105         $t->get_ok("/bees/$newbee")
106           ->status_is(200)
107           ->json_is('/name' => 'Karl');
108
109       The Test::Mojo object is stateful. As long as we haven't started a new
110       transaction by invoking one of the *_ok methods, the request and
111       response objects from the previous transaction are available in the
112       Test::Mojo object:
113
114         # First transaction
115         $t->get_ok('/frogs?q=bullfrog' => {'Content-Type' => 'application/json'})
116           ->status_is(200)
117           ->json_like('/0/species' => qr/catesbeianus/i);
118
119         # Still first transaction
120         $t->content_type_is('application/json');
121
122         # Second transaction
123         $t->get_ok('/frogs?q=banjo' => {'Content-Type' => 'text/html'})
124           ->status_is(200)
125           ->content_like(qr/interioris/i);
126
127         # Still second transaction
128         $t->content_type_is('text/html');
129
130       This statefulness also enables Test::Mojo to handle sessions, follow
131       redirects, and inspect past responses during a redirect.
132
133   The Test::Mojo object
134       The Test::Mojo object manages the Mojolicious application lifecycle (if
135       a Mojolicious application class is supplied) as well as exposes the
136       built-in Mojo::UserAgent object. To create a bare Test::Mojo object:
137
138         my $t = Test::Mojo->new;
139
140       This object initializes a Mojo::UserAgent object and provides a variety
141       of test assertion methods for accessing a web application. For example,
142       with this object, we could test any running web application:
143
144         $t->get_ok('https://www.google.com/')
145           ->status_is(200)
146           ->content_like(qr/search/i);
147
148       You can access the user agent directly if you want to make web requests
149       without triggering test assertions:
150
151         my $tx = $t->ua->post('https://duckduckgo.com/html' => form => {q => 'hypnotoad'});
152         $tx->result->dom->find('a.result__a')->each(sub { say $_->text });
153
154       See Mojo::UserAgent for the complete API and return values.
155
156   Testing Mojolicious applications
157       If you pass the name of a Mojolicious application class (e.g., 'MyApp')
158       to the Test::Mojo constructor, Test::Mojo will instantiate the class
159       and start it, and cause it to listen on a random (unused) port number.
160       Testing a Mojolicious application using Test::Mojo will never conflict
161       with running applications, including the application you're testing.
162
163       The Mojo::UserAgent object in Test::Mojo will know where the
164       application is running and make requests to it. Once the tests have
165       completed, the Mojolicious application will be torn down.
166
167         # Listens on localhost:32114 (some unused TCP port)
168         my $t = Test::Mojo->new('Frogs');
169
170       To test a Mojolicious::Lite application, pass the file path to the
171       application script to the constructor.
172
173         # Load application script relative to the "t" directory
174         use Mojo::File qw(curfile);
175         my $t = Test::Mojo->new(curfile->dirname->sibling('myapp.pl'));
176
177       This object initializes a Mojo::UserAgent object, loads the Mojolicious
178       application "Frogs", binds and listens on a free TCP port (e.g.,
179       32114), and starts the application event loop. When the Test::Mojo
180       object ($t) goes out of scope, the application is stopped.
181
182       Relative URLs in the test object method assertions ("get_ok",
183       "post_ok", etc.) will be sent to the Mojolicious application started by
184       Test::Mojo:
185
186         # Rewritten to "http://localhost:32114/frogs"
187         $t->get_ok('/frogs');
188
189       Test::Mojo has a lot of handy shortcuts built into it to make testing
190       Mojolicious or Mojolicious::Lite applications enjoyable.
191
192       An example
193
194       Let's spin up a Mojolicious application using "mojo generate app
195       MyApp". The "mojo" utility will create a working application and a "t"
196       directory with a working test file:
197
198         $ mojo generate app MyApp
199         [mkdir] /my_app/script
200         [write] /my_app/script/my_app
201         [chmod] /my_app/script/my_app 744
202         ...
203         [mkdir] /my_app/t
204         [write] /my_app/t/basic.t
205         ...
206
207       Let's run the tests (we'll create the "log" directory to quiet the
208       application output):
209
210         $ cd my_app
211         $ mkdir log
212         $ prove -lv t
213         t/basic.t ..
214         ok 1 - GET /
215         ok 2 - 200 OK
216         ok 3 - content is similar
217         1..3
218         ok
219         All tests successful.
220         Files=1, Tests=3,  0 wallclock secs ( 0.03 usr  0.01 sys +  0.33 cusr  0.07 csys =  0.44 CPU)
221         Result: PASS
222
223       The boilerplate test file looks like this:
224
225         use Mojo::Base -strict;
226
227         use Test::More;
228         use Test::Mojo;
229
230         my $t = Test::Mojo->new('MyApp');
231         $t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);
232
233         done_testing();
234
235       Here we can see our application class name "MyApp" is passed to the
236       Test::Mojo constructor. Under the hood, Test::Mojo creates a new
237       Mojo::Server instance, loads "MyApp" (which we just created), and runs
238       the application.  We write our tests with relative URLs because
239       Test::Mojo takes care of getting the request to the running test
240       application (since its port may change between runs).
241
242       Testing with configuration data
243
244       We can alter the behavior of our application using environment
245       variables (such as "MOJO_MODE") and through configuration values. One
246       nice feature of Test::Mojo is its ability to pass configuration values
247       directly from its constructor.
248
249       Let's modify our application and add a "feature flag" to enable a new
250       feature when the "enable_weather" configuration value is set:
251
252         # Load configuration from hash returned by "my_app.conf"
253         my $config = $self->plugin('Config');
254
255         # Normal route to controller
256         $r->get('/')->to('example#welcome');
257
258         # NEW: this route only exists if "enable_weather" is set in the configuration
259         if ($config->{enable_weather}) {
260           $r->get('/weather' => sub ($c) {
261             $c->render(text => "It's hot! 🔥");
262           });
263         }
264
265       To test this new feature, we don't even need to create a configuration
266       file—we can simply pass the configuration data to the application
267       directly via Test::Mojo's constructor:
268
269         my $t = Test::Mojo->new(MyApp => {enable_weather => 1});
270         $t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);
271         $t->get_ok('/weather')->status_is(200)->content_like(qr/🔥/);
272
273       When we run these tests, Test::Mojo will pass this configuration data
274       to the application, which will cause it to create a special "/weather"
275       route that we can access in our tests. Unless "enable_weather" is set
276       in a configuration file, this route will not exist when the application
277       runs. Feature flags like this allow us to do soft rollouts of features,
278       targeting a small audience for a period of time. Once the feature has
279       been proven, we can refactor the conditional and make it a full
280       release.
281
282       This example shows how easy it is to start testing a Mojolicious
283       application and how to set specific application configuration
284       directives from a test file.
285
286       Testing application helpers
287
288       Let's say we register a helper in our application to generate an HTTP
289       Basic Authorization header:
290
291         use Mojo::Util qw(b64_encode);
292
293         app->helper(basic_auth => sub ($c, @values) {
294           return {Authorization => 'Basic ' . b64_encode join(':' => @values), ''};
295         });
296
297       How do we test application helpers like this? Test::Mojo has access to
298       the application object, which allows us to invoke helpers from our test
299       file:
300
301         my $t = Test::Mojo->new('MyApp');
302
303         is_deeply $t->app->basic_auth(bif => "Bif's Passwerdd"), {Authorization => 'Basic YmlmOkJpZidzIFBhc3N3ZXJkZA=='},
304           'correct header value';
305
306       Any aspect of the application (helpers, plugins, routes, etc.) can be
307       introspected from Test::Mojo through the application object. This
308       enables us to get deep test coverage of Mojolicious-based applications.
309

ASSERTIONS

311       This section describes the basic test assertions supplied by
312       Test::Mojo. There are four broad categories of assertions for HTTP
313       requests:
314
315       • HTTP requests
316
317       • HTTP response status
318
319       • HTTP response headers
320
321       • HTTP response content/body
322
323       WebSocket test assertions are covered in "Testing WebSocket web
324       services".
325
326   HTTP request assertions
327       Test::Mojo has a Mojo::UserAgent object that allows it to make HTTP
328       requests and check for HTTP transport errors.  HTTP request assertions
329       include "get_ok", "post_ok", etc. These assertions do not test whether
330       the request was handled successfully, only that the web application
331       handled the request in an HTTP compliant way.
332
333       You may also make HTTP requests using custom verbs (beyond "GET",
334       "POST", "PUT", etc.) by building your own transaction object. See
335       "Custom transactions" below.
336
337       Using HTTP request assertions
338
339       To post a URL-encoded form to the "/calls" endpoint of an application,
340       we simply use the "form" content type shortcut:
341
342         $t->post_ok('/calls' => form => {to => '+43.55.555.5555'});
343
344       Which will create the following HTTP request:
345
346         POST /calls HTTP/1.1
347         Content-Length: 20
348         Content-Type: application/x-www-form-urlencoded
349
350         to=%2B43.55.555.5555
351
352       The *_ok HTTP request assertion methods accept the same arguments as
353       their corresponding Mojo::UserAgent methods (except for the callback
354       argument). This allows us to set headers and build query strings for
355       authentic test situations:
356
357         $t->get_ok('/internal/personnel' => {Authorization => 'Token secret-password'} => form => {q => 'Professor Plum'});
358
359       which generates the following request:
360
361         GET /internal/personnel?q=Professor+Plum HTTP/1.1
362         Content-Length: 0
363         Authorization: Token secret-password
364
365       The "form" content generator (see Mojo::UserAgent::Transactor) will
366       generate a query string for "GET" requests and
367       "application/x-www-form-urlencoded" or "multipart/form-data" for POST
368       requests.
369
370       While these *_ok assertions make the HTTP requests we expect, they tell
371       us little about how well the application handled the request. The
372       application we're testing might have returned any content-type, body,
373       or HTTP status code (200, 302, 400, 404, 500, etc.) and we wouldn't
374       know it.
375
376       Test::Mojo provides assertions to test almost every aspect of the HTTP
377       response, including the HTTP response status code, the value of the
378       "Content-Type" header, and other arbitrary HTTP header information.
379
380   HTTP response status code
381       While not technically an HTTP header, the status line is the first line
382       in an HTTP response and is followed by the response headers. Testing
383       the response status code is common in REST-based and other web
384       applications that use the HTTP status codes to broadly indicate the
385       type of response the server is returning.
386
387       Testing the status code is as simple as adding the "status_is"
388       assertion:
389
390         $t->post_ok('/doorbell' => form => {action => 'ring once'})
391           ->status_is(200);
392
393       Along with "status_isnt", this will cover most needs. For more
394       elaborate status code testing, you can access the response internals
395       directly:
396
397         $t->post_ok('/doorbell' => form => {action => 'ring once'});
398         is $t->tx->res->message, 'Moved Permanently', 'try next door';
399
400   HTTP response headers
401       Test::Mojo allows us to inspect and make assertions about HTTP response
402       headers. The "Content-Type" header is commonly tested and has its own
403       assertion:
404
405         $t->get_ok('/map-of-the-world.pdf')
406           ->content_type_is('application/pdf');
407
408       This is equivalent to the more verbose:
409
410         $t->get_ok('/map-of-the-world.pdf')
411           ->header_is('Content-Type' => 'application/pdf');
412
413       We can test for multiple headers in a single response using method
414       chains:
415
416         $t->get_ok('/map-of-the-world.pdf')
417           ->content_type_is('application/pdf')
418           ->header_isnt('Compression' => 'gzip')
419           ->header_unlike('Server' => qr/IIS/i);
420
421   HTTP response content assertions
422       Test::Mojo also exposes a rich set of assertions for testing the body
423       of a response, whether that body be HTML, plain-text, or JSON. The
424       "content_*" methods look at the body of the response as plain text (as
425       defined by the response's character set):
426
427         $t->get_ok('/scary-things/spiders.json')
428           ->content_is('{"arachnid":"brown recluse"}');
429
430       Although this is a JSON document, "content_is" treats it as if it were
431       a text document. This may be useful for situations where we're looking
432       for a particular string and not concerned with the structure of the
433       document. For example, we can do the same thing with an HTML document:
434
435         $t->get_ok('/scary-things/spiders.html')
436           ->content_like(qr{<title>All The Spiders</title>});
437
438       But because Test::Mojo has access to everything that Mojo::UserAgent
439       does, we can introspect JSON documents as well as DOM-based documents
440       (HTML, XML) with assertions that allow us to check for the existence of
441       elements as well as inspect the content of text nodes.
442
443       JSON response assertions
444
445       Test::Mojo's Mojo::UserAgent has access to a JSON parser, which allows
446       us to test to see if a JSON response contains a value at a location in
447       the document using JSON pointer syntax:
448
449         $t->get_ok('/animals/friendly.json')
450           ->json_has('/beings/jeremiah/age');
451
452       This assertion tells us that the "friendly.json" document contains a
453       value at the "/beings/jeremiah/age" JSON pointer location. We can also
454       inspect the value at JSON pointer locations:
455
456         $t->get_ok('/animals/friendly.json')
457           ->json_has('/beings/jeremiah/age')
458           ->json_is('/beings/jeremiah/age' => 42)
459           ->json_like('/beings/jeremiah/species' => qr/bullfrog/i);
460
461       JSON pointer syntax makes testing JSON responses simple and readable.
462
463       DOM response assertions
464
465       We can also inspect HTML and XML responses using the Mojo::DOM parser
466       in the user agent. Here are a few examples from the Test::Mojo
467       documentation:
468
469         $t->text_is('div.foo[x=y]' => 'Hello!');
470         $t->text_is('html head title' => 'Hello!', 'right title');
471
472       The Mojo::DOM parser uses the CSS selector syntax described in
473       Mojo::DOM::CSS, allowing us to test for values in HTML and XML
474       documents without resorting to typically verbose and inflexible DOM
475       traversal methods.
476

ADVANCED TOPICS

478       This section describes some complex (but common) testing situations
479       that Test::Mojo excels in making simple.
480
481   Redirects
482       The Mojo::UserAgent object in Test::Mojo can handle HTTP redirections
483       internally to whatever level you need.  Let's say we have a web service
484       that redirects "/1" to "/2", "/2" redirects to "/3", "/3" redirects to
485       "/4", and "/4" redirects to "/5":
486
487         GET /1
488
489       returns:
490
491         302 Found
492         Location: /2
493
494       and:
495
496         GET /2
497
498       returns:
499
500         302 Found
501         Location: /3
502
503       and so forth, up to "/5":
504
505         GET /5
506
507       which returns the data we wanted:
508
509         200 OK
510
511         {"message":"this is five"}
512
513       We can tell the user agent in Test::Mojo how to deal with redirects.
514       Each test is making a request to "GET /1", but we vary the number of
515       redirects the user agent should follow with each test:
516
517         my $t = Test::Mojo->new;
518
519         $t->get_ok('/1')
520           ->header_is(location => '/2');
521
522         $t->ua->max_redirects(1);
523         $t->get_ok('/1')
524           ->header_is(location => '/3');
525
526         $t->ua->max_redirects(2);
527         $t->get_ok('/1')
528           ->header_is(location => '/4');
529
530         # Look at the previous hop
531         is $t->tx->previous->res->headers->location, '/3', 'previous redirect';
532
533         $t->ua->max_redirects(3);
534         $t->get_ok('/1')
535           ->header_is(location => '/5');
536
537         $t->ua->max_redirects(4);
538         $t->get_ok('/1')
539           ->json_is('/message' => 'this is five');
540
541       When we set "max_redirects", it stays set for the life of the test
542       object until we change it.
543
544       Test::Mojo's handling of HTTP redirects eliminates the need for making
545       many, sometimes an unknown number, of redirections to keep testing
546       precise and easy to follow (ahem).
547
548   Cookies and session management
549       We can use Test::Mojo to test applications that keep session state in
550       cookies. By default, the Mojo::UserAgent object in Test::Mojo will
551       manage session for us by saving and sending cookies automatically, just
552       like common web browsers:
553
554         use Mojo::Base -strict;
555
556         use Test::More;
557         use Test::Mojo;
558
559         my $t = Test::Mojo->new('MyApp');
560
561         # No authorization cookie
562         $t->get_ok('/')
563           ->status_is(401)
564           ->content_is('Please log in');
565
566         # Application sets an authorization cookie
567         $t->post_ok('/login' => form => {password => 'let me in'})
568           ->status_is(200)
569           ->content_is('You are logged in');
570
571         # Sends the cookie from the previous transaction
572         $t->get_ok('/')
573           ->status_is(200)
574           ->content_like(qr/You logged in at \d+/);
575
576         # Clear the cookies
577         $t->reset_session;
578
579         # No authorization cookie again
580         $t->get_ok('/')
581           ->status_is(401)
582           ->content_is('Please log in');
583
584       We can also inspect cookies in responses for special values through the
585       transaction's response (Mojo::Message::Response) object:
586
587         $t->get_ok('/');
588         like $t->tx->res->cookie('smarty'), qr/smarty=pants/, 'cookie found';
589
590   Custom transactions
591       Let's say we have an application that responds to a new HTTP verb
592       "RING" and to use it we must also pass in a secret cookie value. This
593       is not a problem. We can test the application by creating a
594       Mojo::Transaction object, setting the cookie (see
595       Mojo::Message::Request), then passing the transaction object to
596       "request_ok":
597
598         # Use custom "RING" verb
599         my $tx = $t->ua->build_tx(RING => '/doorbell');
600
601         # Set a special cookie
602         $tx->req->cookies({name => 'Secret', value => "don't tell anybody"});
603
604         # Make the request
605         $t->request_ok($tx)
606           ->status_is(200)
607           ->json_is('/status' => 'ding dong');
608
609   Testing WebSocket web services
610       While the message flow on WebSocket connections can be rather dynamic,
611       it more often than not is quite predictable, which allows this rather
612       pleasant Test::Mojo WebSocket API to be used:
613
614         use Mojo::Base -strict;
615
616         use Test::More;
617         use Test::Mojo;
618
619         # Test echo web service
620         my $t = Test::Mojo->new('EchoService');
621         $t->websocket_ok('/echo')
622           ->send_ok('Hello Mojo!')
623           ->message_ok
624           ->message_is('echo: Hello Mojo!')
625           ->finish_ok;
626
627         # Test JSON web service
628         $t->websocket_ok('/echo.json')
629           ->send_ok({json => {test => [1, 2, 3]}})
630           ->message_ok
631           ->json_message_is('/test' => [1, 2, 3])
632           ->finish_ok;
633
634         done_testing();
635
636       Because of their inherent asynchronous nature, testing WebSocket
637       communications can be tricky. The Test::Mojo WebSocket assertions
638       serialize messages via event loop primitives. This enables us to treat
639       WebSocket messages as if they were using the same request-response
640       communication pattern we're accustomed to with HTTP.
641
642       To illustrate, let's walk through these tests. In the first test, we
643       use the "websocket_ok" assertion to ensure that we can connect to our
644       application's WebSocket route at "/echo" and that it's "speaking"
645       WebSocket protocol to us. The next "send_ok" assertion tests the
646       connection again (in case it closed, for example) and attempts to send
647       the message "Hello Mojo!". The next assertion, "message_ok", blocks
648       (using the Mojo::IOLoop singleton in the application) and waits for a
649       response from the server. The response is then compared with 'echo:
650       Hello Mojo!' in the "message_is" assertion, and finally we close and
651       test our connection status again with "finish_ok".
652
653       The second test is like the first, but now we're sending and expecting
654       JSON documents at "/echo.json". In the "send_ok" assertion we take
655       advantage of Mojo::UserAgent's JSON content generator (see
656       Mojo::UserAgent::Transactor) to marshal hash and array references into
657       JSON documents, and then send them as a WebSocket message. We wait
658       (block) for a response from the server with "message_ok". Then because
659       we're expecting a JSON document back, we can leverage "json_message_ok"
660       which parses the WebSocket response body and returns an object we can
661       access through Mojo::JSON::Pointer syntax. Then we close (and test) our
662       WebSocket connection.
663
664       Testing WebSocket servers does not get any simpler than with
665       Test::Mojo.
666
667   Extending Test::Mojo
668       If you see that you're writing a lot of test assertions that aren't
669       chainable, you may benefit from writing your own test assertions. Let's
670       say we want to test the "Location" header after a redirect. We'll
671       create a new class with Role::Tiny that implements a test assertion
672       named "location_is":
673
674         package Test::Mojo::Role::Location;
675         use Mojo::Base -role, -signatures;
676
677         sub location_is ($self, $value, $desc = "Location: $value") {
678           return $self->test('is', $self->tx->res->headers->location, $value, $desc);
679         }
680
681         1;
682
683       When we make new test assertions using roles, we want to use method
684       signatures that match other *_is methods in Test::Mojo, so here we
685       accept the test object, the value to compare, and an optional
686       description.
687
688       We assign a default description value ($desc), then we use "test" in
689       Test::Mojo to compare the location header with the expected header
690       value, and finally propagates the Test::Mojo object for method
691       chaining.
692
693       With this new package, we're ready to compose a new test object that
694       uses the role:
695
696         my $t = Test::Mojo->with_roles('+Location')->new('MyApp');
697
698         $t->post_ok('/redirect/mojo' => json => {message => 'Mojo, here I come!'})
699           ->status_is(302)
700           ->location_is('http://mojolicious.org')
701           ->or(sub { diag 'I miss tempire.' });
702
703       In this section we've covered how to add custom test assertions to
704       Test::Mojo with roles and how to use those roles to simplify testing.
705

MORE

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

SUPPORT

713       If you have any questions the documentation might not yet answer, don't
714       hesitate to ask in the Forum <https://forum.mojolicious.org> or the
715       official IRC channel "#mojo" on "irc.libera.chat" (chat now!
716       <https://web.libera.chat/#mojo>).
717
718
719
720perl v5.34.0                      2022-01-21   Mojolicious::Guides::Testing(3)
Impressum