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       This object initializes a Mojo::UserAgent object, loads the Mojolicious
171       application "Frogs", binds and listens on a free TCP port (e.g.,
172       32114), and starts the application event loop. When the Test::Mojo
173       object ($t) goes out of scope, the application is stopped.
174
175       Relative URLs in the test object method assertions ("get_ok",
176       "post_ok", etc.) will be sent to the Mojolicious application started by
177       Test::Mojo:
178
179         # Rewritten to "http://localhost:32114/frogs"
180         $t->get_ok('/frogs');
181
182       Test::Mojo has a lot of handy shortcuts built into it to make testing
183       Mojolicious or Mojolicious::Lite applications enjoyable.
184
185       An example
186
187       Let's spin up a Mojolicious application using "mojo generate app
188       MyApp". The "mojo" utility will create a working application and a "t"
189       directory with a working test file:
190
191         $ mojo generate app MyApp
192         [mkdir] /my_app/script
193         [write] /my_app/script/my_app
194         [chmod] /my_app/script/my_app 744
195         ...
196         [mkdir] /my_app/t
197         [write] /my_app/t/basic.t
198         ...
199
200       Let's run the tests (we'll create the "log" directory to quiet the
201       application output):
202
203         $ cd my_app
204         $ mkdir log
205         $ prove -lv t
206         t/basic.t ..
207         ok 1 - GET /
208         ok 2 - 200 OK
209         ok 3 - content is similar
210         1..3
211         ok
212         All tests successful.
213         Files=1, Tests=3,  0 wallclock secs ( 0.03 usr  0.01 sys +  0.33 cusr  0.07 csys =  0.44 CPU)
214         Result: PASS
215
216       The boilerplate test file looks like this:
217
218         use Mojo::Base -strict;
219
220         use Test::More;
221         use Test::Mojo;
222
223         my $t = Test::Mojo->new('MyApp');
224         $t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);
225
226         done_testing();
227
228       Here we can see our application class name "MyApp" is passed to the
229       Test::Mojo constructor. Under the hood, Test::Mojo creates a new
230       Mojo::Server instance, loads "MyApp" (which we just created), and runs
231       the application.  We write our tests with relative URLs because
232       Test::Mojo takes care of getting the request to the running test
233       application (since its port may change between runs).
234
235       Testing with configuration data
236
237       We can alter the behavior of our application using environment
238       variables (such as "MOJO_MODE") and through configuration values. One
239       nice feature of Test::Mojo is its ability to pass configuration values
240       directly from its constructor.
241
242       Let's modify our application and add a "feature flag" to enable a new
243       feature when the "enable_weather" configuration value is set:
244
245         # Load configuration from hash returned by "my_app.conf"
246         my $config = $self->plugin('Config');
247
248         # Normal route to controller
249         $r->get('/')->to('example#welcome');
250
251         # NEW: this route only exists if "enable_weather" is set in the configuration
252         if ($config->{enable_weather}) {
253           $r->get('/weather' => sub ($c) {
254             $c->render(text => "It's hot! 🔥");
255           });
256         }
257
258       To test this new feature, we don't even need to create a configuration
259       file—we can simply pass the configuration data to the application
260       directly via Test::Mojo's constructor:
261
262         my $t = Test::Mojo->new(MyApp => {enable_weather => 1});
263         $t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);
264         $t->get_ok('/weather')->status_is(200)->content_like(qr/🔥/);
265
266       When we run these tests, Test::Mojo will pass this configuration data
267       to the application, which will cause it to create a special "/weather"
268       route that we can access in our tests. Unless "enable_weather" is set
269       in a configuration file, this route will not exist when the application
270       runs. Feature flags like this allow us to do soft rollouts of features,
271       targeting a small audience for a period of time. Once the feature has
272       been proven, we can refactor the conditional and make it a full
273       release.
274
275       This example shows how easy it is to start testing a Mojolicious
276       application and how to set specific application configuration
277       directives from a test file.
278
279       Testing application helpers
280
281       Let's say we register a helper in our application to generate an HTTP
282       Basic Authorization header:
283
284         use Mojo::Util qw(b64_encode);
285
286         app->helper(basic_auth => sub ($c, @values) {
287           return {Authorization => 'Basic ' . b64_encode join(':' => @values), ''};
288         });
289
290       How do we test application helpers like this? Test::Mojo has access to
291       the application object, which allows us to invoke helpers from our test
292       file:
293
294         my $t = Test::Mojo->new('MyApp');
295
296         is_deeply $t->app->basic_auth(bif => "Bif's Passwerdd"), {Authorization => 'Basic YmlmOkJpZidzIFBhc3N3ZXJkZA=='},
297           'correct header value';
298
299       Any aspect of the application (helpers, plugins, routes, etc.) can be
300       introspected from Test::Mojo through the application object. This
301       enables us to get deep test coverage of Mojolicious-based applications.
302

ASSERTIONS

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

ADVANCED TOPICS

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

MORE

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

SUPPORT

706       If you have any questions the documentation might not yet answer, don't
707       hesitate to ask in the Forum <https://forum.mojolicious.org> or the
708       official IRC channel "#mojo" on "chat.freenode.net" (chat now!
709       <https://webchat.freenode.net/#mojo>).
710
711
712
713perl v5.32.1                      2021-02-07   Mojolicious::Guides::Testing(3)
Impressum