source: main/trunk/greenstone2/perllib/cpan/Mojolicious/Guides/Testing.pod@ 32205

Last change on this file since 32205 was 32205, checked in by ak19, 6 years ago

First set of commits to do with implementing the new 'paged_html' output option of PDFPlugin that uses using xpdftools' new pdftohtml. So far tested only on Linux (64 bit), but things work there so I'm optimistically committing the changes since they work. 2. Committing the pre-built Linux binaries of XPDFtools for both 32 and 64 bit built by the XPDF group. 2. To use the correct bitness variant of xpdftools, setup.bash now exports the BITNESS env var, consulted by gsConvert.pl. 3. All the perl code changes to do with using xpdf tools' pdftohtml to generate paged_html and feed it in the desired form into GS(3): gsConvert.pl, PDFPlugin.pm and its parent ConvertBinaryPFile.pm have been modified to make it all work. xpdftools' pdftohtml generates a folder containing an html file and a screenshot for each page in a PDF (as well as an index.html linking to each page's html). However, we want a single html file that contains each individual 'page' html's content in a div, and need to do some further HTML style, attribute and structure modifications to massage the xpdftool output to what we want for GS. In order to parse and manipulate the HTML 'DOM' to do this, we're using the Mojo::DOM package that Dr Bainbridge found and which he's compiled up. Mojo::DOM is therefore also committed in this revision. Some further changes and some display fixes are required, but need to check with the others about that.

File size: 24.5 KB
Line 
1
2=encoding utf8
3
4=head1 NAME
5
6Mojolicious::Guides::Testing - Web Application Testing Made Easy
7
8=head1 OVERVIEW
9
10This document is an introduction to testing web applications with L<Test::Mojo>.
11L<Test::Mojo> can be thought of as a module that provides all of the tools and
12testing assertions needed to test web applications in a Perl-ish way.
13
14While L<Test::Mojo> can be used to test any web application, it has shortcuts
15designed to make testing L<Mojolicious> web applications easy and pain-free.
16
17Please refer to the L<Test::Mojo> documentation for a complete reference to many
18of the ideas and syntax introduced in this document.
19
20A test file for a simple web application might look like:
21
22 use Mojo::Base -strict;
23
24 use Test::Mojo;
25 use Test::More;
26
27 # Start a Mojolicious app named "Celestial"
28 my $t = Test::Mojo->new('Celestial');
29
30 # Post a JSON document
31 $t->post_ok('/notifications' => json => {event => 'full moon'})
32 ->status_is(201)
33 ->json_is('/message' => 'notification created');
34
35 # Perform GET requests and look at the responses
36 $t->get_ok('/sunrise')
37 ->status_is(200)
38 ->content_like(qr/ am$/);
39 $t->get_ok('/sunset')
40 ->status_is(200)
41 ->content_like(qr/ pm$/);
42
43 # Post a URL-encoded form
44 $t->post_ok('/insurance' => form => {name => 'Jimmy', amount => '€3.000.000'})
45 ->status_is(200);
46
47 # Use Test::More's like() to check the response
48 like $t->tx->res->dom->at('div#thanks')->text, qr/thank you/, 'thanks';
49
50 done_testing();
51
52In the rest of this document we'll explore these concepts and others related to
53L<Test::Mojo>.
54
55=head1 CONCEPTS
56
57Essentials every L<Mojolicious> developer should know.
58
59=head2 L<Test::Mojo> at a glance
60
61The L<Test::More> module bundled with Perl includes several primitive test
62assertions, such as C<ok>, C<is>, C<isnt>, C<like>, C<unlike>, C<cmp_ok>, etc.
63An assertion "passes" if its expression returns a true value. The assertion
64method prints "ok" or "not ok" if an assertion passes or fails (respectively).
65
66L<Test::Mojo> supplies additional test assertions organized around the web
67application request/response transaction (transport, response headers, response
68bodies, etc.), and WebSocket communications.
69
70One interesting thing of note: the return value of L<Test::Mojo> object
71assertions is always the test object itself, allowing us to "chain" test
72assertion methods. So rather than grouping related test statements like this:
73
74 $t->get_ok('/frogs');
75 $t->status_is(200);
76 $t->content_like(qr/bullfrog/);
77 $t->content_like(qr/hypnotoad/);
78
79Method chaining allows us to connect test assertions that belong together:
80
81 $t->get_ok('/frogs')
82 ->status_is(200)
83 ->content_like(qr/bullfrog/)
84 ->content_like(qr/hypnotoad/);
85
86This makes for a much more I<concise> and I<coherent> testing experience:
87concise because we are not repeating the invocant for each test, and coherent
88because assertions that belong to the same request are syntactically bound in
89the same method chain.
90
91Occasionally it makes sense to break up a test to perform more complex
92assertions on a response. L<Test::Mojo> exposes the entire transaction object so
93you can get all the data you need from a response:
94
95 $t->put_ok('/bees' => json => {type => 'worker', name => 'Karl'})
96 ->status_is(202)
97 ->json_has('/id');
98
99 # Pull out the id from the response
100 my $newbee = $t->tx->res->json('/id');
101
102 # Make a new request with data from the previous response
103 $t->get_ok("/bees/$newbee")
104 ->status_is(200)
105 ->json_is('/name' => 'Karl');
106
107The L<Test::Mojo> object is I<stateful>. As long as we haven't started a new
108transaction by invoking one of the C<*_ok> methods, the request and response
109objects from the previous transaction are available in the L<Test::Mojo>
110object:
111
112 # First transaction
113 $t->get_ok('/frogs?q=bullfrog' => {'Content-Type' => 'application/json'})
114 ->status_is(200)
115 ->json_like('/0/species' => qr/catesbeianus/i);
116
117 # Still first transaction
118 $t->content_type_is('application/json');
119
120 # Second transaction
121 $t->get_ok('/frogs?q=banjo' => {'Content-Type' => 'text/html'})
122 ->status_is(200)
123 ->content_like(qr/interioris/i);
124
125 # Still second transaction
126 $t->content_type_is('text/html');
127
128This statefulness also enables L<Test::Mojo> to handle sessions, follow
129redirects, and inspect past responses during a redirect.
130
131=head2 The L<Test::Mojo> object
132
133The L<Test::Mojo> object manages the Mojolicious application lifecycle (if a
134Mojolicious application class is supplied) as well as exposes the built-in
135L<Mojo::UserAgent> object. To create a bare L<Test::Mojo> object:
136
137 my $t = Test::Mojo->new;
138
139This object initializes a L<Mojo::UserAgent> object and provides a variety of
140test assertion methods for accessing a web application. For example, with this
141object, we could test any running web application:
142
143 $t->get_ok('https://www.google.com/')
144 ->status_is(200)
145 ->content_like(qr/search/i);
146
147You can access the user agent directly if you want to make web requests without
148triggering test assertions:
149
150 my $tx = $t->ua->post(
151 'https://duckduckgo.com/html' => form => {q => 'hypnotoad'});
152 $tx->result->dom->find('a.result__a')->each(sub { say $_->text });
153
154See L<Mojo::UserAgent> for the complete API and return values.
155
156=head2 Testing Mojolicious applications
157
158If you pass the name of a L<Mojolicious> application class (e.g., 'MyApp') to
159the L<Test::Mojo> constructor, L<Test::Mojo> will instantiate the class and
160start it, and cause it to listen on a random (unused) port number. Testing a
161Mojolicious application using L<Test::Mojo> will never conflict with running
162applications, including the application you're testing.
163
164The L<Mojo::UserAgent> object in L<Test::Mojo> will know where the application
165is running and make requests to it. Once the tests have completed, the
166L<Mojolicious> application will be torn down.
167
168 # Listens on localhost:32114 (some unused TCP port)
169 my $t = Test::Mojo->new('Frogs');
170
171This object initializes a L<Mojo::UserAgent> object, loads the Mojolicious
172application C<Frogs>, binds and listens on a free TCP port (e.g., 32114), and
173starts the application event loop. When the L<Test::Mojo> object (C<$t>) goes
174out of scope, the application is stopped.
175
176Relative URLs in the test object method assertions (C<get_ok>, C<post_ok>, etc.)
177will be sent to the Mojolicious application started by L<Test::Mojo>:
178
179 # Rewritten to "http://localhost:32114/frogs"
180 $t->get_ok('/frogs');
181
182L<Test::Mojo> has a lot of handy shortcuts built into it to make testing
183L<Mojolicious> or L<Mojolicious::Lite> applications enjoyable.
184
185=head3 An example
186
187Let's spin up a Mojolicious application using C<mojo generate app MyApp>. The
188C<mojo> utility will create a working application and a C<t> directory with a
189working 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
200Let's run the tests (we'll create the C<log> directory to quiet the application
201output):
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
214 csys = 0.44 CPU)
215 Result: PASS
216
217The boilerplate test file looks like this:
218
219 use Mojo::Base -strict;
220
221 use Test::More;
222 use Test::Mojo;
223
224 my $t = Test::Mojo->new('MyApp');
225 $t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);
226
227 done_testing();
228
229Here we can see our application class name C<MyApp> is passed to the
230L<Test::Mojo> constructor. Under the hood, L<Test::Mojo> creates a new
231L<Mojo::Server> instance, loads C<MyApp> (which we just created), and runs the
232application. We write our tests with relative URLs because L<Test::Mojo> takes
233care of getting the request to the running test application (since its port may
234change between runs).
235
236=head3 Testing with configuration data
237
238We can alter the behavior of our application using environment variables (such
239as C<MOJO_MODE>) and through configuration values. One nice feature of
240L<Test::Mojo> is its ability to pass configuration values directly from its
241constructor.
242
243Let's modify our application and add a "feature flag" to enable a new feature
244when the C<enable_weather> configuration value is set:
245
246 # Load configuration from hash returned by "my_app.conf"
247 my $config = $self->plugin('Config');
248
249 # Normal route to controller
250 $r->get('/')->to('example#welcome');
251
252 # NEW: this route only exists if "enable_weather" is set in the configuration
253 if ($config->{enable_weather}) {
254 $r->get('/weather' => sub { shift->render(text => "It's hot! 🔥") }
255 }
256
257To test this new feature, we don't even need to create a configuration file—we
258can simply pass the configuration data to the application directly via
259L<Test::Mojo>'s constructor:
260
261 my $t = Test::Mojo->new(MyApp => {enable_weather => 1});
262 $t->get_ok('/')->status_is(200)->content_like(qr/Mojolicious/i);
263 $t->get_ok('/weather')->status_is(200)->content_like(qr/🔥/);
264
265When we run these tests, L<Test::Mojo> will pass this configuration data to the
266application, which will cause it to create a special C</weather> route that we
267can access in our tests. Unless C<enable_weather> is set in a configuration
268file, this route will not exist when the application runs. Feature flags like
269this allow us to do soft rollouts of features, targeting a small audience for a
270period of time. Once the feature has been proven, we can refactor the
271conditional and make it a full release.
272
273This example shows how easy it is to start testing a Mojolicious application and
274how to set specific application configuration directives from a test file.
275
276=head3 Testing application helpers
277
278Let's say we register a helper in our application to generate an HTTP Basic
279Authorization header:
280
281 use Mojo::Util 'b64_encode';
282
283 app->helper(basic_auth => sub {
284 my ($c, @values) = @_;
285 return {Authorization => 'Basic ' . b64_encode join(':' => @values), ''};
286 });
287
288How do we test application helpers like this? L<Test::Mojo> has access to the
289application object, which allows us to invoke helpers from our test file:
290
291 my $t = Test::Mojo->new('MyApp');
292
293 is_deeply $t->app->basic_auth(bif => "Bif's Passwerdd"),
294 {Authorization => 'Basic YmlmOkJpZidzIFBhc3N3ZXJkZA=='},
295 'correct header value';
296
297Any aspect of the application (helpers, plugins, routes, etc.) can be
298introspected from L<Test::Mojo> through the application object. This enables us
299to get deep test coverage of L<Mojolicious>-based applications.
300
301=head1 ASSERTIONS
302
303This section describes the basic test assertions supplied by L<Test::Mojo>.
304There are four broad categories of assertions for HTTP requests:
305
306=over 2
307
308=item * HTTP requests
309
310=item * HTTP response status
311
312=item * HTTP response headers
313
314=item * HTTP response content/body
315
316=back
317
318WebSocket test assertions are covered in L</Testing WebSocket web services>.
319
320=head2 HTTP request assertions
321
322L<Test::Mojo> has a L<Mojo::UserAgent> object that allows it to make HTTP
323requests and check for HTTP transport errors. HTTP request assertions include
324C<get_ok>, C<post_ok>, etc. These assertions do not test whether the request
325was handled I<successfully>, only that the web application handled the request
326in an HTTP compliant way.
327
328You may also make HTTP requests using custom verbs (beyond C<GET>, C<POST>,
329C<PUT>, etc.) by building your own transaction object. See
330L</"Custom transactions"> below.
331
332=head3 Using HTTP request assertions
333
334To post a URL-encoded form to the C</calls> endpoint of an application, we
335simply use the C<form> content type shortcut:
336
337 $t->post_ok('/calls' => form => {to => '+43.55.555.5555'});
338
339Which will create the following HTTP request:
340
341 POST /calls HTTP/1.1
342 Content-Length: 20
343 Content-Type: application/x-www-form-urlencoded
344
345 to=%2B43.55.555.5555
346
347The C<*_ok> HTTP request assertion methods accept the same arguments as their
348corresponding L<Mojo::UserAgent> methods (except for the callback argument).
349This allows us to set headers and build query strings for authentic test
350situations:
351
352 $t->get_ok('/internal/personnel' => {Authorization => 'Token secret-password'}
353 => form => {q => 'Professor Plum'});
354
355which generates the following request:
356
357 GET /internal/personnel?q=Professor+Plum HTTP/1.1
358 Content-Length: 0
359 Authorization: Token secret-password
360
361The C<form> content generator (see L<Mojo::UserAgent::Transactor>) will generate
362a query string for C<GET> requests and C<application/x-www-form-urlencoded> or
363C<multipart/form-data> for POST requests.
364
365While these C<*_ok> assertions make the HTTP I<requests> we expect, they tell us
366little about I<how well> the application handled the request. The application
367we're testing might have returned any content-type, body, or HTTP status code
368(200, 302, 400, 404, 500, etc.) and we wouldn't know it.
369
370L<Test::Mojo> provides assertions to test almost every aspect of the HTTP
371response, including the HTTP response status code, the value of the
372C<Content-Type> header, and other arbitrary HTTP header information.
373
374=head2 HTTP response status code
375
376While not technically an HTTP header, the status line is the first line in an
377HTTP response and is followed by the response headers. Testing the response
378status code is common in REST-based and other web applications that use the HTTP
379status codes to broadly indicate the type of response the server is returning.
380
381Testing the status code is as simple as adding the C<status_is> assertion:
382
383 $t->post_ok('/doorbell' => form => {action => 'ring once'})
384 ->status_is(200);
385
386Along with C<status_isnt>, this will cover most needs. For more elaborate status
387code testing, you can access the response internals directly:
388
389 $t->post_ok('/doorbell' => form => {action => 'ring once'});
390 is $t->tx->res->message, 'Moved Permanently', 'try next door';
391
392=head2 HTTP response headers
393
394L<Test::Mojo> allows us to inspect and make assertions about HTTP response
395headers. The C<Content-Type> header is commonly tested and has its own
396assertion:
397
398 $t->get_ok('/map-of-the-world.pdf')
399 ->content_type_is('application/pdf');
400
401This is equivalent to the more verbose:
402
403 $t->get_ok('/map-of-the-world.pdf')
404 ->header_is('Content-Type' => 'application/pdf');
405
406We can test for multiple headers in a single response using method chains:
407
408 $t->get_ok('/map-of-the-world.pdf')
409 ->content_type_is('application/pdf')
410 ->header_isnt('Compression' => 'gzip')
411 ->header_unlike('Server' => qr/IIS/i);
412
413=head2 HTTP response content assertions
414
415L<Test::Mojo> also exposes a rich set of assertions for testing the body of a
416response, whether that body be HTML, plain-text, or JSON. The C<content_*>
417methods look at the body of the response as plain text (as defined by the
418response's character set):
419
420 $t->get_ok('/scary-things/spiders.json')
421 ->content_is('{"arachnid":"brown recluse"}');
422
423Although this is a JSON document, C<content_is> treats it as if it were a text
424document. This may be useful for situations where we're looking for a particular
425string and not concerned with the structure of the document. For example, we can
426do 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
431But because L<Test::Mojo> has access to everything that L<Mojo::UserAgent> does,
432we can introspect JSON documents as well as DOM-based documents (HTML, XML) with
433assertions that allow us to check for the existence of elements as well as
434inspect the content of text nodes.
435
436=head3 JSON response assertions
437
438L<Test::Mojo>'s L<Mojo::UserAgent> has access to a JSON parser, which allows us
439to test to see if a JSON response contains a value at a location in the document
440using JSON pointer syntax:
441
442 $t->get_ok('/animals/friendly.json')
443 ->json_has('/beings/jeremiah/age');
444
445This assertion tells us that the C<friendly.json> document contains a value at
446the C</beings/jeremiah/age> JSON pointer location. We can also inspect the value
447at 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
454JSON pointer syntax makes testing JSON responses simple and readable.
455
456=head3 DOM response assertions
457
458We can also inspect HTML and XML responses using the L<Mojo::DOM> parser in the
459user agent. Here are a few examples from the L<Test::Mojo> documentation:
460
461 $t->text_is('div.foo[x=y]' => 'Hello!');
462 $t->text_is('html head title' => 'Hello!', 'right title');
463
464The L<Mojo::DOM> parser uses the CSS selector syntax described in
465L<Mojo::DOM::CSS>, allowing us to test for values in HTML and XML documents
466without resorting to typically verbose and inflexible DOM traversal methods.
467
468=head1 ADVANCED TOPICS
469
470This section describes some complex (but common) testing situations that
471L<Test::Mojo> excels in making simple.
472
473=head2 Redirects
474
475The L<Mojo::UserAgent> object in L<Test::Mojo> can handle HTTP redirections
476internally to whatever level you need. Let's say we have a web service that
477redirects C</1> to C</2>, C</2> redirects to C</3>, C</3> redirects to C</4>,
478and C</4> redirects to C</5>:
479
480 GET /1
481
482returns:
483
484 302 Found
485 Location: /2
486
487and:
488
489 GET /2
490
491returns:
492
493 302 Found
494 Location: /3
495
496and so forth, up to C</5>:
497
498 GET /5
499
500which returns the data we wanted:
501
502 200 OK
503
504 {"message":"this is five"}
505
506We can tell the user agent in L<Test::Mojo> how to deal with redirects. Each
507test is making a request to C<GET /1>, but we vary the number of redirects the
508user 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
534When we set C<max_redirects>, it stays set for the life of the test object until
535we change it.
536
537L<Test::Mojo>'s handling of HTTP redirects eliminates the need for making many,
538sometimes an unknown number, of redirections to keep testing precise and easy to
539follow (ahem).
540
541=head2 Cookies and session management
542
543We can use L<Test::Mojo> to test applications that keep session state in
544cookies. By default, the L<Mojo::UserAgent> object in L<Test::Mojo> will manage
545session for us by saving and sending cookies automatically, just like common web
546browsers:
547
548 use Mojo::Base -strict;
549
550 use Test::More;
551 use Test::Mojo;
552
553 my $t = Test::Mojo->new('MyApp');
554
555 # No authorization cookie
556 $t->get_ok('/')
557 ->status_is(401)
558 ->content_is('Please log in');
559
560 # Application sets an authorization cookie
561 $t->post_ok('/login' => form => {password => 'let me in'})
562 ->status_is(200)
563 ->content_is('You are logged in');
564
565 # Sends the cookie from the previous transaction
566 $t->get_ok('/')
567 ->status_is(200)
568 ->content_like(qr/You logged in at \d+/);
569
570 # Clear the cookies
571 $t->reset_session;
572
573 # No authorization cookie again
574 $t->get_ok('/')
575 ->status_is(401)
576 ->content_is('Please log in');
577
578We can also inspect cookies in responses for special values through the
579transaction's response (L<Mojo::Message::Response>) object:
580
581 $t->get_ok('/');
582 like $t->tx->res->cookie('smarty'), qr/smarty=pants/, 'cookie found';
583
584=head2 Custom transactions
585
586Let's say we have an application that responds to a new HTTP verb C<RING> and to
587use it we must also pass in a secret cookie value. This is not a problem. We can
588test the application by creating a L<Mojo::Transaction> object, setting the
589cookie (see L<Mojo::Message::Request>), then passing the transaction object to
590C<request_ok>:
591
592 # Use custom "RING" verb
593 my $tx = $t->ua->build_tx(RING => '/doorbell');
594
595 # Set a special cookie
596 $tx->req->cookies({name => 'Secret', value => "don't tell anybody"});
597
598 # Make the request
599 $t->request_ok($tx)
600 ->status_is(200)
601 ->json_is('/status' => 'ding dong');
602
603=head2 Testing WebSocket web services
604
605While the message flow on WebSocket connections can be rather dynamic, it more
606often than not is quite predictable, which allows this rather pleasant
607L<Test::Mojo> WebSocket API to be used:
608
609 use Mojo::Base -strict;
610
611 use Test::More;
612 use Test::Mojo;
613
614 # Test echo web service
615 my $t = Test::Mojo->new('EchoService');
616 $t->websocket_ok('/echo')
617 ->send_ok('Hello Mojo!')
618 ->message_ok
619 ->message_is('echo: Hello Mojo!')
620 ->finish_ok;
621
622 # Test JSON web service
623 $t->websocket_ok('/echo.json')
624 ->send_ok({json => {test => [1, 2, 3]}})
625 ->message_ok
626 ->json_message_is('/test' => [1, 2, 3])
627 ->finish_ok;
628
629 done_testing();
630
631Because of their inherent asynchronous nature, testing WebSocket communications
632can be tricky. The L<Test::Mojo> WebSocket assertions serialize messages via
633event loop primitives. This enables us to treat WebSocket messages as if they
634were using the same request-response communication pattern we're accustomed to
635with HTTP.
636
637To illustrate, let's walk through these tests. In the first test, we use the
638C<websocket_ok> assertion to ensure that we can connect to our application's
639WebSocket route at C</echo> and that it's "speaking" WebSocket protocol to us.
640The next C<send_ok> assertion tests the connection again (in case it closed, for
641example) and attempts to send the message C<Hello Mojo!>. The next assertion,
642C<message_ok>, blocks (using the L<Mojo::IOLoop> singleton in the application)
643and waits for a response from the server. The response is then compared with
644C<'echo: Hello Mojo!'> in the C<message_is> assertion, and finally we close and
645test our connection status again with C<finish_ok>.
646
647The second test is like the first, but now we're sending and expecting JSON
648documents at C</echo.json>. In the C<send_ok> assertion we take advantage of
649L<Mojo::UserAgent>'s JSON content generator (see L<Mojo::UserAgent::Transactor>)
650to marshal hash and array references into JSON documents, and then send them as
651a WebSocket message. We wait (block) for a response from the server with
652C<message_ok>. Then because we're expecting a JSON document back, we can
653leverage C<json_message_ok> which parses the WebSocket response body and returns
654an object we can access through L<Mojo::JSON::Pointer> syntax. Then we close
655(and test) our WebSocket connection.
656
657Testing WebSocket servers does not get any simpler than with L<Test::Mojo>.
658
659=head2 Extending L<Test::Mojo>
660
661If you see that you're writing a lot of test assertions that aren't chainable,
662you may benefit from writing your own test assertions. Let's say we want to test
663the C<Location> header after a redirect. We'll create a new class with
664L<Role::Tiny> that implements a test assertion named C<location_is>:
665
666 package Test::Mojo::Role::Location;
667 use Mojo::Base -role;
668
669 use Test::More;
670
671 sub location_is {
672 my ($t, $value, $desc) = @_;
673 $desc ||= "Location: $value";
674 local $Test::Builder::Level = $Test::Builder::Level + 1;
675 return $t->success(is($t->tx->res->headers->location, $value, $desc));
676 }
677
678 1;
679
680When we make new test assertions using roles, we want to use method signatures
681that match other C<*_is> methods in L<Test::Mojo>, so here we accept the test
682object, the value to compare, and an optional description.
683
684We assign a default description value (C<$desc>), set the L<Test::Builder>
685C<Level> global variable one level higher (which tells L<Test::Builder> how far
686up the call stack to look when something fails), then we use L<Test::More>'s
687C<is> function to compare the location header with the expected header value. We
688wrap that in the C<success> attribute, which records the boolean test result and
689propagates the L<Test::Mojo> object for method chaining.
690
691With this new package, we're ready to compose a new test object that uses the
692role:
693
694 my $t = Test::Mojo->with_roles('+Location')->new('MyApp');
695
696 $t->post_ok('/redirect/mojo' => json => {message => 'Mojo, here I come!'})
697 ->status_is(302)
698 ->location_is('http://mojolicious.org')
699 ->or(sub { diag 'I miss tempire.' });
700
701In this section we've covered how to add custom test assertions to L<Test::Mojo>
702with roles and how to use those roles to simplify testing.
703
704=head1 MORE
705
706You can continue with L<Mojolicious::Guides> now or take a look at the
707L<Mojolicious wiki|http://github.com/kraih/mojo/wiki>, which contains a lot more
708documentation and examples by many different authors.
709
710=head1 SUPPORT
711
712If you have any questions the documentation might not yet answer, don't
713hesitate to ask on the
714L<mailing list|http://groups.google.com/group/mojolicious> or the official IRC
715channel C<#mojo> on C<irc.perl.org>
716(L<chat now!|https://chat.mibbit.com/?channel=%23mojo&server=irc.perl.org>).
717
718=cut
Note: See TracBrowser for help on using the repository browser.