File: Testing.pod

package info (click to toggle)
libmojolicious-perl 9.39%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,324 kB
  • sloc: perl: 10,319; makefile: 31; javascript: 1
file content (649 lines) | stat: -rw-r--r-- 24,931 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649

=encoding utf8

=head1 NAME

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

=head1 OVERVIEW

This document is an introduction to testing web applications with L<Test::Mojo>. L<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 L<Test::Mojo> can be used to test any web application, it has shortcuts designed to make testing L<Mojolicious>
web applications easy and pain-free.

Please refer to the L<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 L<Test::Mojo>.

=head1 CONCEPTS

Essentials every L<Mojolicious> developer should know.

=head2 L<Test::Mojo> at a glance

The L<Test::More> module bundled with Perl includes several primitive test assertions, such as C<ok>, C<is>, C<isnt>,
C<like>, C<unlike>, C<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).

L<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 L<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 I<concise> and I<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. L<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 L<Test::Mojo> object is I<stateful>. As long as we haven't started a new transaction by invoking one of the C<*_ok>
methods, the request and response objects from the previous transaction are available in the L<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 L<Test::Mojo> to handle sessions, follow redirects, and inspect past responses during a
redirect.

=head2 The L<Test::Mojo> object

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

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

This object initializes a L<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 L<Mojo::UserAgent> for the complete API and return values.

=head2 Testing Mojolicious applications

If you pass the name of a L<Mojolicious> application class (e.g., 'MyApp') to the L<Test::Mojo> constructor,
L<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 L<Test::Mojo> will never conflict with running applications, including the application
you're testing.

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

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

To test a L<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 L<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 L<Test::Mojo> object (C<$t>) goes out of scope, the
application is stopped.

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

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

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

=head3 An example

Let's spin up a Mojolicious application using C<mojo generate app MyApp>. The C<mojo> utility will create a working
application and a C<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 C<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 C<MyApp> is passed to the L<Test::Mojo> constructor. Under the hood,
L<Test::Mojo> creates a new L<Mojo::Server> instance, loads C<MyApp> (which we just created), and runs the application.
We write our tests with relative URLs because L<Test::Mojo> takes care of getting the request to the running test
application (since its port may change between runs).

=head3 Testing with configuration data

We can alter the behavior of our application using environment variables (such as C<MOJO_MODE>) and through
configuration values. One nice feature of L<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 C<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 L<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, L<Test::Mojo> will pass this configuration data to the application, which will cause it to
create a special C</weather> route that we can access in our tests. Unless C<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.

=head3 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? L<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 L<Test::Mojo> through the
application object. This enables us to get deep test coverage of L<Mojolicious>-based applications.

=head1 ASSERTIONS

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

=over 2

=item * HTTP requests

=item * HTTP response status

=item * HTTP response headers

=item * HTTP response content/body

=back

WebSocket test assertions are covered in L</Testing WebSocket web services>.

=head2 HTTP request assertions

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

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

=head3 Using HTTP request assertions

To post a URL-encoded form to the C</calls> endpoint of an application, we simply use the C<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 C<*_ok> HTTP request assertion methods accept the same arguments as their corresponding L<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 C<form> content generator (see L<Mojo::UserAgent::Transactor>) will generate a query string for C<GET> requests and
C<application/x-www-form-urlencoded> or C<multipart/form-data> for POST requests.

While these C<*_ok> assertions make the HTTP I<requests> we expect, they tell us little about I<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.

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

=head2 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 C<status_is> assertion:

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

Along with C<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';

=head2 HTTP response headers

L<Test::Mojo> allows us to inspect and make assertions about HTTP response headers. The C<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);

=head2 HTTP response content assertions

L<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 C<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, C<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 L<Test::Mojo> has access to everything that L<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.

=head3 JSON response assertions

L<Test::Mojo>'s L<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 C<friendly.json> document contains a value at the C</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.

=head3 DOM response assertions

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

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

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

=head1 ADVANCED TOPICS

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

=head2 Redirects

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

  GET /1

returns:

  302 Found
  Location: /2

and:

  GET /2

returns:

  302 Found
  Location: /3

and so forth, up to C</5>:

  GET /5

which returns the data we wanted:

  200 OK

  {"message":"this is five"}

We can tell the user agent in L<Test::Mojo> how to deal with redirects. Each test is making a request to C<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 C<max_redirects>, it stays set for the life of the test object until we change it.

L<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).

=head2 Cookies and session management

We can use L<Test::Mojo> to test applications that keep session state in cookies. By default, the L<Mojo::UserAgent>
object in L<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
(L<Mojo::Message::Response>) object:

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

=head2 Custom transactions

Let's say we have an application that responds to a new HTTP verb C<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 L<Mojo::Transaction> object, setting the
cookie (see L<Mojo::Message::Request>), then passing the transaction object to C<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');

=head2 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 L<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 L<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 C<websocket_ok> assertion to ensure that
we can connect to our application's WebSocket route at C</echo> and that it's "speaking" WebSocket protocol to us. The
next C<send_ok> assertion tests the connection again (in case it closed, for example) and attempts to send the message
C<Hello Mojo!>. The next assertion, C<message_ok>, blocks (using the L<Mojo::IOLoop> singleton in the application) and
waits for a response from the server. The response is then compared with C<'echo: Hello Mojo!'> in the C<message_is>
assertion, and finally we close and test our connection status again with C<finish_ok>.

The second test is like the first, but now we're sending and expecting JSON documents at C</echo.json>. In the
C<send_ok> assertion we take advantage of L<Mojo::UserAgent>'s JSON content generator (see
L<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 C<message_ok>. Then because we're expecting a
JSON document back, we can leverage C<json_message_ok> which parses the WebSocket response body and returns an object
we can access through L<Mojo::JSON::Pointer> syntax. Then we close (and test) our WebSocket connection.

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

=head2 Extending L<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 C<Location> header after a redirect. We'll create a new class with
L<Role::Tiny> that implements a test assertion named C<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 C<*_is> methods in
L<Test::Mojo>, so here we accept the test object, the value to compare, and an optional description.

We assign a default description value (C<$desc>), then we use L<Test::Mojo/"test"> to compare the location header with
the expected header value, and finally propagates the L<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 L<Test::Mojo> with roles and how to use those roles
to simplify testing.

=head1 MORE

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

=head1 SUPPORT

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

=cut