.\" -*- mode: troff; coding: utf-8 -*- .\" Automatically generated by Pod::Man 5.01 (Pod::Simple 3.43) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" \*(C` and \*(C' are quotes in nroff, nothing in troff, for use with C<>. .ie n \{\ . ds C` "" . ds C' "" 'br\} .el\{\ . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" ======================================================================== .\" .IX Title "Test::Mojo 3pm" .TH Test::Mojo 3pm 2024-05-15 "perl v5.38.2" "User Contributed Perl Documentation" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH NAME Test::Mojo \- Testing Mojo .SH SYNOPSIS .IX Header "SYNOPSIS" .Vb 2 \& use Test::More; \& use Test::Mojo; \& \& my $t = Test::Mojo\->new(\*(AqMyApp\*(Aq); \& \& # HTML/XML \& $t\->get_ok(\*(Aq/welcome\*(Aq)\->status_is(200)\->text_is(\*(Aqdiv#message\*(Aq => \*(AqHello!\*(Aq); \& \& # JSON \& $t\->post_ok(\*(Aq/search.json\*(Aq => form => {q => \*(AqPerl\*(Aq}) \& \->status_is(200) \& \->header_is(\*(AqServer\*(Aq => \*(AqMojolicious (Perl)\*(Aq) \& \->header_isnt(\*(AqX\-Bender\*(Aq => \*(AqBite my shiny metal ass!\*(Aq) \& \->json_is(\*(Aq/results/4/title\*(Aq => \*(AqPerl rocks!\*(Aq) \& \->json_like(\*(Aq/results/7/title\*(Aq => qr/Perl/); \& \& # WebSocket \& $t\->websocket_ok(\*(Aq/echo\*(Aq) \& \->send_ok(\*(Aqhello\*(Aq) \& \->message_ok \& \->message_is(\*(Aqecho: hello\*(Aq) \& \->finish_ok; \& \& done_testing(); .Ve .SH DESCRIPTION .IX Header "DESCRIPTION" Test::Mojo is a test user agent based on Mojo::UserAgent, it is usually used together with Test::More to test Mojolicious applications. Just run your tests with prove. .PP .Vb 2 \& $ prove \-l \-v \& $ prove \-l \-v t/foo.t .Ve .PP If it is not already defined, the \f(CW\*(C`MOJO_LOG_LEVEL\*(C'\fR environment variable will be set to \f(CW\*(C`trace\*(C'\fR or \f(CW\*(C`fatal\*(C'\fR, depending on the value of the \f(CW\*(C`HARNESS_IS_VERBOSE\*(C'\fR environment variable. And to make it esier to test HTTPS/WSS web services "insecure" in Mojo::UserAgent will be activated by default for "ua". .PP See Mojolicious::Guides::Testing for more. .SH ATTRIBUTES .IX Header "ATTRIBUTES" Test::Mojo implements the following attributes. .SS handler .IX Subsection "handler" .Vb 2 \& my $cb = $t\->handler; \& $t = $t\->handler(sub {...}); .Ve .PP A callback to connect Test::Mojo with Test::More. .PP .Vb 3 \& $t\->handler(sub ($name, @args) { \& return Test::More\->can($name)\->(@args); \& }); .Ve .SS message .IX Subsection "message" .Vb 2 \& my $msg = $t\->message; \& $t = $t\->message([text => $bytes]); .Ve .PP Current WebSocket message represented as an array reference containing the frame type and payload. .PP .Vb 5 \& # More specific tests \& use Mojo::JSON qw(decode_json); \& my $hash = decode_json $t\->message\->[1]; \& is ref $hash, \*(AqHASH\*(Aq, \*(Aqright reference\*(Aq; \& is $hash\->{foo}, \*(Aqbar\*(Aq, \*(Aqright value\*(Aq; \& \& # Test custom message \& $t\->message([binary => $bytes]) \& \->json_message_has(\*(Aq/foo/bar\*(Aq) \& \->json_message_hasnt(\*(Aq/bar\*(Aq) \& \->json_message_is(\*(Aq/foo/baz\*(Aq => {yada => [1, 2, 3]}); .Ve .SS success .IX Subsection "success" .Vb 2 \& my $bool = $t\->success; \& $t = $t\->success($bool); .Ve .PP True if the last test was successful. .PP .Vb 10 \& # Build custom tests \& my $location_is = sub ($t, $value, $desc = \*(Aq\*(Aq) { \& $desc ||= "Location: $value"; \& local $Test::Builder::Level = $Test::Builder::Level + 1; \& return $t\->success(is($t\->tx\->res\->headers\->location, $value, $desc)); \& }; \& $t\->get_ok(\*(Aq/\*(Aq) \& \->status_is(302) \& \->$location_is(\*(Aqhttps://mojolicious.org\*(Aq) \& \->or(sub { diag \*(AqMust have been Joel!\*(Aq }); .Ve .SS tx .IX Subsection "tx" .Vb 2 \& my $tx = $t\->tx; \& $t = $t\->tx(Mojo::Transaction::HTTP\->new); .Ve .PP Current transaction, usually a Mojo::Transaction::HTTP or Mojo::Transaction::WebSocket object. .PP .Vb 4 \& # More specific tests \& is $t\->tx\->res\->json\->{foo}, \*(Aqbar\*(Aq, \*(Aqright value\*(Aq; \& ok $t\->tx\->res\->content\->is_multipart, \*(Aqmultipart content\*(Aq; \& is $t\->tx\->previous\->res\->code, 302, \*(Aqright status\*(Aq; .Ve .SS ua .IX Subsection "ua" .Vb 2 \& my $ua = $t\->ua; \& $t = $t\->ua(Mojo::UserAgent\->new); .Ve .PP User agent used for testing, defaults to a Mojo::UserAgent object. .PP .Vb 3 \& # Allow redirects \& $t\->ua\->max_redirects(10); \& $t\->get_ok(\*(Aq/redirect\*(Aq)\->status_is(200)\->content_like(qr/redirected/); \& \& # Switch protocol from HTTP to HTTPS \& $t\->ua\->server\->url(\*(Aqhttps\*(Aq); \& $t\->get_ok(\*(Aq/secure\*(Aq)\->status_is(200)\->content_like(qr/secure/); \& \& # Use absolute URL for request with Basic authentication \& my $url = $t\->ua\->server\->url\->userinfo(\*(Aqsri:secr3t\*(Aq)\->path(\*(Aq/secrets.json\*(Aq); \& $t\->post_ok($url => json => {limit => 10}) \& \->status_is(200) \& \->json_is(\*(Aq/1/content\*(Aq, \*(AqMojo rocks!\*(Aq); \& \& # Customize all transactions (including followed redirects) \& $t\->ua\->on(start => sub ($ua, $tx) { $tx\->req\->headers\->accept_language(\*(Aqen\-US\*(Aq) }); \& $t\->get_ok(\*(Aq/hello\*(Aq)\->status_is(200)\->content_like(qr/Howdy/); .Ve .SH METHODS .IX Header "METHODS" Test::Mojo inherits all methods from Mojo::Base and implements the following new ones. .SS app .IX Subsection "app" .Vb 2 \& my $app = $t\->app; \& $t = $t\->app(Mojolicious\->new); .Ve .PP Access application with "app" in Mojo::UserAgent::Server. .PP .Vb 2 \& # Change log level \& $t\->app\->log\->level(\*(Aqfatal\*(Aq); \& \& # Test application directly \& is $t\->app\->defaults\->{foo}, \*(Aqbar\*(Aq, \*(Aqright value\*(Aq; \& ok $t\->app\->routes\->find(\*(Aqecho\*(Aq)\->is_websocket, \*(AqWebSocket route\*(Aq; \& my $c = $t\->app\->build_controller; \& ok $c\->render(template => \*(Aqfoo\*(Aq), \*(Aqrendering was successful\*(Aq; \& is $c\->res\->status, 200, \*(Aqright status\*(Aq; \& is $c\->res\->body, \*(AqFoo!\*(Aq, \*(Aqright content\*(Aq; \& \& # Change application behavior \& $t\->app\->hook(before_dispatch => sub ($c) { \& $c\->render(text => \*(AqThis request did not reach the router.\*(Aq) if $c\->req\->url\->path\->contains(\*(Aq/user\*(Aq); \& }); \& $t\->get_ok(\*(Aq/user\*(Aq)\->status_is(200)\->content_like(qr/not reach the router/); \& \& # Extract additional information \& my $stash; \& $t\->app\->hook(after_dispatch => sub ($c) { $stash = $c\->stash }); \& $t\->get_ok(\*(Aq/hello\*(Aq)\->status_is(200); \& is $stash\->{foo}, \*(Aqbar\*(Aq, \*(Aqright value\*(Aq; .Ve .SS attr_is .IX Subsection "attr_is" .Vb 2 \& $t = $t\->attr_is(\*(Aqimg.cat\*(Aq, \*(Aqalt\*(Aq, \*(AqGrumpy cat\*(Aq); \& $t = $t\->attr_is(\*(Aqimg.cat\*(Aq, \*(Aqalt\*(Aq, \*(AqGrumpy cat\*(Aq, \*(Aqright alt text\*(Aq); .Ve .PP Checks text content of attribute with "attr" in Mojo::DOM at the CSS selectors first matching HTML/XML element for exact match with "at" in Mojo::DOM. .SS attr_isnt .IX Subsection "attr_isnt" .Vb 2 \& $t = $t\->attr_isnt(\*(Aqimg.cat\*(Aq, \*(Aqalt\*(Aq, \*(AqCalm cat\*(Aq); \& $t = $t\->attr_isnt(\*(Aqimg.cat\*(Aq, \*(Aqalt\*(Aq, \*(AqCalm cat\*(Aq, \*(Aqdifferent alt text\*(Aq); .Ve .PP Opposite of "attr_is". .SS attr_like .IX Subsection "attr_like" .Vb 2 \& $t = $t\->attr_like(\*(Aqimg.cat\*(Aq, \*(Aqalt\*(Aq, qr/Grumpy/); \& $t = $t\->attr_like(\*(Aqimg.cat\*(Aq, \*(Aqalt\*(Aq, qr/Grumpy/, \*(Aqright alt text\*(Aq); .Ve .PP Checks text content of attribute with "attr" in Mojo::DOM at the CSS selectors first matching HTML/XML element for similar match with "at" in Mojo::DOM. .SS attr_unlike .IX Subsection "attr_unlike" .Vb 2 \& $t = $t\->attr_unlike(\*(Aqimg.cat\*(Aq, \*(Aqalt\*(Aq, qr/Calm/); \& $t = $t\->attr_unlike(\*(Aqimg.cat\*(Aq, \*(Aqalt\*(Aq, qr/Calm/, \*(Aqdifferent alt text\*(Aq); .Ve .PP Opposite of "attr_like". .SS content_is .IX Subsection "content_is" .Vb 2 \& $t = $t\->content_is(\*(Aqworking!\*(Aq); \& $t = $t\->content_is(\*(Aqworking!\*(Aq, \*(Aqright content\*(Aq); .Ve .PP Check response content for exact match after retrieving it from "text" in Mojo::Message. .SS content_isnt .IX Subsection "content_isnt" .Vb 2 \& $t = $t\->content_isnt(\*(Aqworking!\*(Aq); \& $t = $t\->content_isnt(\*(Aqworking!\*(Aq, \*(Aqdifferent content\*(Aq); .Ve .PP Opposite of "content_is". .SS content_like .IX Subsection "content_like" .Vb 2 \& $t = $t\->content_like(qr/working!/); \& $t = $t\->content_like(qr/working!/, \*(Aqright content\*(Aq); .Ve .PP Check response content for similar match after retrieving it from "text" in Mojo::Message. .SS content_type_is .IX Subsection "content_type_is" .Vb 2 \& $t = $t\->content_type_is(\*(Aqtext/html\*(Aq); \& $t = $t\->content_type_is(\*(Aqtext/html\*(Aq, \*(Aqright content type\*(Aq); .Ve .PP Check response \f(CW\*(C`Content\-Type\*(C'\fR header for exact match. .SS content_type_isnt .IX Subsection "content_type_isnt" .Vb 2 \& $t = $t\->content_type_isnt(\*(Aqtext/html\*(Aq); \& $t = $t\->content_type_isnt(\*(Aqtext/html\*(Aq, \*(Aqdifferent content type\*(Aq); .Ve .PP Opposite of "content_type_is". .SS content_type_like .IX Subsection "content_type_like" .Vb 2 \& $t = $t\->content_type_like(qr/text/); \& $t = $t\->content_type_like(qr/text/, \*(Aqright content type\*(Aq); .Ve .PP Check response \f(CW\*(C`Content\-Type\*(C'\fR header for similar match. .SS content_type_unlike .IX Subsection "content_type_unlike" .Vb 2 \& $t = $t\->content_type_unlike(qr/text/); \& $t = $t\->content_type_unlike(qr/text/, \*(Aqdifferent content type\*(Aq); .Ve .PP Opposite of "content_type_like". .SS content_unlike .IX Subsection "content_unlike" .Vb 2 \& $t = $t\->content_unlike(qr/working!/); \& $t = $t\->content_unlike(qr/working!/, \*(Aqdifferent content\*(Aq); .Ve .PP Opposite of "content_like". .SS delete_ok .IX Subsection "delete_ok" .Vb 5 \& $t = $t\->delete_ok(\*(Aqhttp://example.com/foo\*(Aq); \& $t = $t\->delete_ok(\*(Aq/foo\*(Aq); \& $t = $t\->delete_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => \*(AqContent!\*(Aq); \& $t = $t\->delete_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => form => {a => \*(Aqb\*(Aq}); \& $t = $t\->delete_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => json => {a => \*(Aqb\*(Aq}); .Ve .PP Perform a \f(CW\*(C`DELETE\*(C'\fR request and check for transport errors, takes the same arguments as "delete" in Mojo::UserAgent, except for the callback. .SS element_count_is .IX Subsection "element_count_is" .Vb 2 \& $t = $t\->element_count_is(\*(Aqdiv.foo[x=y]\*(Aq, 5); \& $t = $t\->element_count_is(\*(Aqhtml body div\*(Aq, 30, \*(Aqthirty elements\*(Aq); .Ve .PP Checks the number of HTML/XML elements matched by the CSS selector with "find" in Mojo::DOM. .SS element_exists .IX Subsection "element_exists" .Vb 2 \& $t = $t\->element_exists(\*(Aqdiv.foo[x=y]\*(Aq); \& $t = $t\->element_exists(\*(Aqhtml head title\*(Aq, \*(Aqhas a title\*(Aq); .Ve .PP Checks for existence of the CSS selectors first matching HTML/XML element with "at" in Mojo::DOM. .PP .Vb 7 \& # Check attribute values \& $t\->get_ok(\*(Aq/login\*(Aq) \& \->element_exists(\*(Aqlabel[for=email]\*(Aq) \& \->element_exists(\*(Aqinput[name=email][type=text][value*="example.com"]\*(Aq) \& \->element_exists(\*(Aqlabel[for=pass]\*(Aq) \& \->element_exists(\*(Aqinput[name=pass][type=password]\*(Aq) \& \->element_exists(\*(Aqinput[type=submit][value]\*(Aq); .Ve .SS element_exists_not .IX Subsection "element_exists_not" .Vb 2 \& $t = $t\->element_exists_not(\*(Aqdiv.foo[x=y]\*(Aq); \& $t = $t\->element_exists_not(\*(Aqhtml head title\*(Aq, \*(Aqhas no title\*(Aq); .Ve .PP Opposite of "element_exists". .SS finish_ok .IX Subsection "finish_ok" .Vb 3 \& $t = $t\->finish_ok; \& $t = $t\->finish_ok(1000); \& $t = $t\->finish_ok(1003 => \*(AqCannot accept data!\*(Aq); .Ve .PP Close WebSocket connection gracefully. .SS finished_ok .IX Subsection "finished_ok" .Vb 1 \& $t = $t\->finished_ok(1000); .Ve .PP Wait for WebSocket connection to be closed gracefully and check status. .SS get_ok .IX Subsection "get_ok" .Vb 5 \& $t = $t\->get_ok(\*(Aqhttp://example.com/foo\*(Aq); \& $t = $t\->get_ok(\*(Aq/foo\*(Aq); \& $t = $t\->get_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => \*(AqContent!\*(Aq); \& $t = $t\->get_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => form => {a => \*(Aqb\*(Aq}); \& $t = $t\->get_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => json => {a => \*(Aqb\*(Aq}); .Ve .PP Perform a \f(CW\*(C`GET\*(C'\fR request and check for transport errors, takes the same arguments as "get" in Mojo::UserAgent, except for the callback. .PP .Vb 2 \& # Run tests against remote host \& $t\->get_ok(\*(Aqhttps://docs.mojolicious.org\*(Aq)\->status_is(200); \& \& # Use relative URL for request with Basic authentication \& $t\->get_ok(\*(Aq//sri:secr3t@/secrets.json\*(Aq) \& \->status_is(200) \& \->json_is(\*(Aq/1/content\*(Aq, \*(AqMojo rocks!\*(Aq); \& \& # Run additional tests on the transaction \& $t\->get_ok(\*(Aq/foo\*(Aq)\->status_is(200); \& is $t\->tx\->res\->dom\->at(\*(Aqinput\*(Aq)\->val, \*(Aqwhatever\*(Aq, \*(Aqright value\*(Aq; .Ve .SS head_ok .IX Subsection "head_ok" .Vb 5 \& $t = $t\->head_ok(\*(Aqhttp://example.com/foo\*(Aq); \& $t = $t\->head_ok(\*(Aq/foo\*(Aq); \& $t = $t\->head_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => \*(AqContent!\*(Aq); \& $t = $t\->head_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => form => {a => \*(Aqb\*(Aq}); \& $t = $t\->head_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => json => {a => \*(Aqb\*(Aq}); .Ve .PP Perform a \f(CW\*(C`HEAD\*(C'\fR request and check for transport errors, takes the same arguments as "head" in Mojo::UserAgent, except for the callback. .SS header_exists .IX Subsection "header_exists" .Vb 2 \& $t = $t\->header_exists(\*(AqETag\*(Aq); \& $t = $t\->header_exists(\*(AqETag\*(Aq, \*(Aqheader exists\*(Aq); .Ve .PP Check if response header exists. .SS header_exists_not .IX Subsection "header_exists_not" .Vb 2 \& $t = $t\->header_exists_not(\*(AqETag\*(Aq); \& $t = $t\->header_exists_not(\*(AqETag\*(Aq, \*(Aqheader is missing\*(Aq); .Ve .PP Opposite of "header_exists". .SS header_is .IX Subsection "header_is" .Vb 2 \& $t = $t\->header_is(ETag => \*(Aq"abc321"\*(Aq); \& $t = $t\->header_is(ETag => \*(Aq"abc321"\*(Aq, \*(Aqright header\*(Aq); .Ve .PP Check response header for exact match. .SS header_isnt .IX Subsection "header_isnt" .Vb 2 \& $t = $t\->header_isnt(Etag => \*(Aq"abc321"\*(Aq); \& $t = $t\->header_isnt(ETag => \*(Aq"abc321"\*(Aq, \*(Aqdifferent header\*(Aq); .Ve .PP Opposite of "header_is". .SS header_like .IX Subsection "header_like" .Vb 2 \& $t = $t\->header_like(ETag => qr/abc/); \& $t = $t\->header_like(ETag => qr/abc/, \*(Aqright header\*(Aq); .Ve .PP Check response header for similar match. .SS header_unlike .IX Subsection "header_unlike" .Vb 2 \& $t = $t\->header_unlike(ETag => qr/abc/); \& $t = $t\->header_unlike(ETag => qr/abc/, \*(Aqdifferent header\*(Aq); .Ve .PP Opposite of "header_like". .SS json_has .IX Subsection "json_has" .Vb 2 \& $t = $t\->json_has(\*(Aq/foo\*(Aq); \& $t = $t\->json_has(\*(Aq/minibar\*(Aq, \*(Aqhas a minibar\*(Aq); .Ve .PP Check if JSON response contains a value that can be identified using the given JSON Pointer with Mojo::JSON::Pointer. .SS json_hasnt .IX Subsection "json_hasnt" .Vb 2 \& $t = $t\->json_hasnt(\*(Aq/foo\*(Aq); \& $t = $t\->json_hasnt(\*(Aq/minibar\*(Aq, \*(Aqno minibar\*(Aq); .Ve .PP Opposite of "json_has". .SS json_is .IX Subsection "json_is" .Vb 3 \& $t = $t\->json_is({foo => [1, 2, 3]}); \& $t = $t\->json_is(\*(Aq/foo\*(Aq => [1, 2, 3]); \& $t = $t\->json_is(\*(Aq/foo/1\*(Aq => 2, \*(Aqright value\*(Aq); .Ve .PP Check the value extracted from JSON response using the given JSON Pointer with Mojo::JSON::Pointer, which defaults to the root value if it is omitted. .PP .Vb 2 \& # Use an empty JSON Pointer to test the whole JSON response with a test description \& $t\->json_is(\*(Aq\*(Aq => {foo => [1, 2, 3]}, \*(Aqright object\*(Aq); .Ve .SS json_like .IX Subsection "json_like" .Vb 2 \& $t = $t\->json_like(\*(Aq/foo/1\*(Aq => qr/^\ed+$/); \& $t = $t\->json_like(\*(Aq/foo/1\*(Aq => qr/^\ed+$/, \*(Aqright value\*(Aq); .Ve .PP Check the value extracted from JSON response using the given JSON Pointer with Mojo::JSON::Pointer for similar match. .SS json_message_has .IX Subsection "json_message_has" .Vb 2 \& $t = $t\->json_message_has(\*(Aq/foo\*(Aq); \& $t = $t\->json_message_has(\*(Aq/minibar\*(Aq, \*(Aqhas a minibar\*(Aq); .Ve .PP Check if JSON WebSocket message contains a value that can be identified using the given JSON Pointer with Mojo::JSON::Pointer. .SS json_message_hasnt .IX Subsection "json_message_hasnt" .Vb 2 \& $t = $t\->json_message_hasnt(\*(Aq/foo\*(Aq); \& $t = $t\->json_message_hasnt(\*(Aq/minibar\*(Aq, \*(Aqno minibar\*(Aq); .Ve .PP Opposite of "json_message_has". .SS json_message_is .IX Subsection "json_message_is" .Vb 3 \& $t = $t\->json_message_is({foo => [1, 2, 3]}); \& $t = $t\->json_message_is(\*(Aq/foo\*(Aq => [1, 2, 3]); \& $t = $t\->json_message_is(\*(Aq/foo/1\*(Aq => 2, \*(Aqright value\*(Aq); .Ve .PP Check the value extracted from JSON WebSocket message using the given JSON Pointer with Mojo::JSON::Pointer, which defaults to the root value if it is omitted. .SS json_message_like .IX Subsection "json_message_like" .Vb 2 \& $t = $t\->json_message_like(\*(Aq/foo/1\*(Aq => qr/^\ed+$/); \& $t = $t\->json_message_like(\*(Aq/foo/1\*(Aq => qr/^\ed+$/, \*(Aqright value\*(Aq); .Ve .PP Check the value extracted from JSON WebSocket message using the given JSON Pointer with Mojo::JSON::Pointer for similar match. .SS json_message_unlike .IX Subsection "json_message_unlike" .Vb 2 \& $t = $t\->json_message_unlike(\*(Aq/foo/1\*(Aq => qr/^\ed+$/); \& $t = $t\->json_message_unlike(\*(Aq/foo/1\*(Aq => qr/^\ed+$/, \*(Aqdifferent value\*(Aq); .Ve .PP Opposite of "json_message_like". .SS json_unlike .IX Subsection "json_unlike" .Vb 2 \& $t = $t\->json_unlike(\*(Aq/foo/1\*(Aq => qr/^\ed+$/); \& $t = $t\->json_unlike(\*(Aq/foo/1\*(Aq => qr/^\ed+$/, \*(Aqdifferent value\*(Aq); .Ve .PP Opposite of "json_like". .SS message_is .IX Subsection "message_is" .Vb 4 \& $t = $t\->message_is({binary => $bytes}); \& $t = $t\->message_is({text => $bytes}); \& $t = $t\->message_is(\*(Aqworking!\*(Aq); \& $t = $t\->message_is(\*(Aqworking!\*(Aq, \*(Aqright message\*(Aq); .Ve .PP Check WebSocket message for exact match. .SS message_isnt .IX Subsection "message_isnt" .Vb 4 \& $t = $t\->message_isnt({binary => $bytes}); \& $t = $t\->message_isnt({text => $bytes}); \& $t = $t\->message_isnt(\*(Aqworking!\*(Aq); \& $t = $t\->message_isnt(\*(Aqworking!\*(Aq, \*(Aqdifferent message\*(Aq); .Ve .PP Opposite of "message_is". .SS message_like .IX Subsection "message_like" .Vb 4 \& $t = $t\->message_like({binary => qr/$bytes/}); \& $t = $t\->message_like({text => qr/$bytes/}); \& $t = $t\->message_like(qr/working!/); \& $t = $t\->message_like(qr/working!/, \*(Aqright message\*(Aq); .Ve .PP Check WebSocket message for similar match. .SS message_ok .IX Subsection "message_ok" .Vb 2 \& $t = $t\->message_ok; \& $t = $t\->message_ok(\*(Aqgot a message\*(Aq); .Ve .PP Wait for next WebSocket message to arrive. .PP .Vb 6 \& # Wait for message and perform multiple tests on it \& $t\->websocket_ok(\*(Aq/time\*(Aq) \& \->message_ok \& \->message_like(qr/\ed+/) \& \->message_unlike(qr/\ew+/) \& \->finish_ok; .Ve .SS message_unlike .IX Subsection "message_unlike" .Vb 4 \& $t = $t\->message_unlike({binary => qr/$bytes/}); \& $t = $t\->message_unlike({text => qr/$bytes/}); \& $t = $t\->message_unlike(qr/working!/); \& $t = $t\->message_unlike(qr/working!/, \*(Aqdifferent message\*(Aq); .Ve .PP Opposite of "message_like". .SS new .IX Subsection "new" .Vb 7 \& my $t = Test::Mojo\->new; \& my $t = Test::Mojo\->new(\*(AqMyApp\*(Aq); \& my $t = Test::Mojo\->new(\*(AqMyApp\*(Aq, {foo => \*(Aqbar\*(Aq}); \& my $t = Test::Mojo\->new(Mojo::File\->new(\*(Aq/path/to/myapp.pl\*(Aq)); \& my $t = Test::Mojo\->new(Mojo::File\->new(\*(Aq/path/to/myapp.pl\*(Aq), {foo => \*(Aqbar\*(Aq}); \& my $t = Test::Mojo\->new(MyApp\->new); \& my $t = Test::Mojo\->new(MyApp\->new, {foo => \*(Aqbar\*(Aq}); .Ve .PP Construct a new Test::Mojo object. In addition to a class name or Mojo::File object pointing to the application script, you can pass along a hash reference with configuration values that will be used to override the application configuration. The special configuration value \f(CW\*(C`config_override\*(C'\fR will be set in "config" in Mojolicious as well, which is used to disable configuration plugins like Mojolicious::Plugin::Config, Mojolicious::Plugin::JSONConfig and Mojolicious::Plugin::NotYAMLConfig for tests. .PP .Vb 3 \& # Load application script relative to the "t" directory \& use Mojo::File qw(curfile); \& my $t = Test::Mojo\->new(curfile\->dirname\->sibling(\*(Aqmyapp.pl\*(Aq)); .Ve .SS options_ok .IX Subsection "options_ok" .Vb 5 \& $t = $t\->options_ok(\*(Aqhttp://example.com/foo\*(Aq); \& $t = $t\->options_ok(\*(Aq/foo\*(Aq); \& $t = $t\->options_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => \*(AqContent!\*(Aq); \& $t = $t\->options_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => form => {a => \*(Aqb\*(Aq}); \& $t = $t\->options_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => json => {a => \*(Aqb\*(Aq}); .Ve .PP Perform a \f(CW\*(C`OPTIONS\*(C'\fR request and check for transport errors, takes the same arguments as "options" in Mojo::UserAgent, except for the callback. .SS or .IX Subsection "or" .Vb 1 \& $t = $t\->or(sub {...}); .Ve .PP Execute callback if the value of "success" is false. .PP .Vb 3 \& # Diagnostics \& $t\->get_ok(\*(Aq/bad\*(Aq)\->or(sub { diag \*(AqMust have been Glen!\*(Aq }) \& \->status_is(200)\->or(sub { diag $t\->tx\->res\->dom\->at(\*(Aqtitle\*(Aq)\->text }); .Ve .SS patch_ok .IX Subsection "patch_ok" .Vb 5 \& $t = $t\->patch_ok(\*(Aqhttp://example.com/foo\*(Aq); \& $t = $t\->patch_ok(\*(Aq/foo\*(Aq); \& $t = $t\->patch_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => \*(AqContent!\*(Aq); \& $t = $t\->patch_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => form => {a => \*(Aqb\*(Aq}); \& $t = $t\->patch_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => json => {a => \*(Aqb\*(Aq}); .Ve .PP Perform a \f(CW\*(C`PATCH\*(C'\fR request and check for transport errors, takes the same arguments as "patch" in Mojo::UserAgent, except for the callback. .SS post_ok .IX Subsection "post_ok" .Vb 5 \& $t = $t\->post_ok(\*(Aqhttp://example.com/foo\*(Aq); \& $t = $t\->post_ok(\*(Aq/foo\*(Aq); \& $t = $t\->post_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => \*(AqContent!\*(Aq); \& $t = $t\->post_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => form => {a => \*(Aqb\*(Aq}); \& $t = $t\->post_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => json => {a => \*(Aqb\*(Aq}); .Ve .PP Perform a \f(CW\*(C`POST\*(C'\fR request and check for transport errors, takes the same arguments as "post" in Mojo::UserAgent, except for the callback. .PP .Vb 3 \& # Test file upload \& my $upload = {foo => {content => \*(Aqbar\*(Aq, filename => \*(Aqbaz.txt\*(Aq}}; \& $t\->post_ok(\*(Aq/upload\*(Aq => form => $upload)\->status_is(200); \& \& # Test JSON API \& $t\->post_ok(\*(Aq/hello.json\*(Aq => json => {hello => \*(Aqworld\*(Aq}) \& \->status_is(200) \& \->json_is({bye => \*(Aqworld\*(Aq}); .Ve .SS put_ok .IX Subsection "put_ok" .Vb 5 \& $t = $t\->put_ok(\*(Aqhttp://example.com/foo\*(Aq); \& $t = $t\->put_ok(\*(Aq/foo\*(Aq); \& $t = $t\->put_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => \*(AqContent!\*(Aq); \& $t = $t\->put_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => form => {a => \*(Aqb\*(Aq}); \& $t = $t\->put_ok(\*(Aq/foo\*(Aq => {Accept => \*(Aq*/*\*(Aq} => json => {a => \*(Aqb\*(Aq}); .Ve .PP Perform a \f(CW\*(C`PUT\*(C'\fR request and check for transport errors, takes the same arguments as "put" in Mojo::UserAgent, except for the callback. .SS request_ok .IX Subsection "request_ok" .Vb 1 \& $t = $t\->request_ok(Mojo::Transaction::HTTP\->new); .Ve .PP Perform request and check for transport errors. .PP .Vb 3 \& # Request with custom method \& my $tx = $t\->ua\->build_tx(FOO => \*(Aq/test.json\*(Aq => json => {foo => 1}); \& $t\->request_ok($tx)\->status_is(200)\->json_is({success => 1}); \& \& # Request with custom cookie \& my $tx = $t\->ua\->build_tx(GET => \*(Aq/account\*(Aq); \& $tx\->req\->cookies({name => \*(Aquser\*(Aq, value => \*(Aqsri\*(Aq}); \& $t\->request_ok($tx)\->status_is(200)\->text_is(\*(Aqhead > title\*(Aq => \*(AqHello sri\*(Aq); \& \& # Custom WebSocket handshake \& my $tx = $t\->ua\->build_websocket_tx(\*(Aq/foo\*(Aq); \& $tx\->req\->headers\->remove(\*(AqUser\-Agent\*(Aq); \& $t\->request_ok($tx)\->message_ok\->message_is(\*(Aqbar\*(Aq)\->finish_ok; .Ve .SS reset_session .IX Subsection "reset_session" .Vb 1 \& $t = $t\->reset_session; .Ve .PP Reset user agent session. .SS send_ok .IX Subsection "send_ok" .Vb 6 \& $t = $t\->send_ok({binary => $bytes}); \& $t = $t\->send_ok({text => $bytes}); \& $t = $t\->send_ok({json => {test => [1, 2, 3]}}); \& $t = $t\->send_ok([$fin, $rsv1, $rsv2, $rsv3, $op, $payload]); \& $t = $t\->send_ok($chars); \& $t = $t\->send_ok($chars, \*(Aqsent successfully\*(Aq); .Ve .PP Send message or frame via WebSocket. .PP .Vb 6 \& # Send JSON object as "Text" message \& $t\->websocket_ok(\*(Aq/echo.json\*(Aq) \& \->send_ok({json => {test => \*(AqI ♥ Mojolicious!\*(Aq}}) \& \->message_ok \& \->json_message_is(\*(Aq/test\*(Aq => \*(AqI ♥ Mojolicious!\*(Aq) \& \->finish_ok; .Ve .SS status_is .IX Subsection "status_is" .Vb 2 \& $t = $t\->status_is(200); \& $t = $t\->status_is(200, \*(Aqright status\*(Aq); .Ve .PP Check response status for exact match. .SS status_isnt .IX Subsection "status_isnt" .Vb 2 \& $t = $t\->status_isnt(200); \& $t = $t\->status_isnt(200, \*(Aqdifferent status\*(Aq); .Ve .PP Opposite of "status_is". .SS test .IX Subsection "test" .Vb 1 \& $t = $t\->test(\*(Aqis\*(Aq, \*(Aqfirst value\*(Aq, \*(Aqsecond value\*(Aq, \*(Aqright value\*(Aq); .Ve .PP Call Test::More functions through "handler", used to implement Test::Mojo roles. The result will be stored in "success". .SS text_is .IX Subsection "text_is" .Vb 2 \& $t = $t\->text_is(\*(Aqdiv.foo[x=y]\*(Aq => \*(AqHello!\*(Aq); \& $t = $t\->text_is(\*(Aqhtml head title\*(Aq => \*(AqHello!\*(Aq, \*(Aqright title\*(Aq); .Ve .PP Checks text content of the CSS selectors first matching HTML/XML element for exact match with "at" in Mojo::DOM. .SS text_isnt .IX Subsection "text_isnt" .Vb 2 \& $t = $t\->text_isnt(\*(Aqdiv.foo[x=y]\*(Aq => \*(AqHello!\*(Aq); \& $t = $t\->text_isnt(\*(Aqhtml head title\*(Aq => \*(AqHello!\*(Aq, \*(Aqdifferent title\*(Aq); .Ve .PP Opposite of "text_is". .SS text_like .IX Subsection "text_like" .Vb 2 \& $t = $t\->text_like(\*(Aqdiv.foo[x=y]\*(Aq => qr/Hello/); \& $t = $t\->text_like(\*(Aqhtml head title\*(Aq => qr/Hello/, \*(Aqright title\*(Aq); .Ve .PP Checks text content of the CSS selectors first matching HTML/XML element for similar match with "at" in Mojo::DOM. .SS text_unlike .IX Subsection "text_unlike" .Vb 2 \& $t = $t\->text_unlike(\*(Aqdiv.foo[x=y]\*(Aq => qr/Hello/); \& $t = $t\->text_unlike(\*(Aqhtml head title\*(Aq => qr/Hello/, \*(Aqdifferent title\*(Aq); .Ve .PP Opposite of "text_like". .SS websocket_ok .IX Subsection "websocket_ok" .Vb 3 \& $t = $t\->websocket_ok(\*(Aqhttp://example.com/echo\*(Aq); \& $t = $t\->websocket_ok(\*(Aq/echo\*(Aq); \& $t = $t\->websocket_ok(\*(Aq/echo\*(Aq => {DNT => 1} => [\*(Aqv1.proto\*(Aq]); .Ve .PP Open a WebSocket connection with transparent handshake, takes the same arguments as "websocket" in Mojo::UserAgent, except for the callback. .PP .Vb 6 \& # WebSocket with permessage\-deflate compression \& $t\->websocket_ok(\*(Aq/\*(Aq => {\*(AqSec\-WebSocket\-Extensions\*(Aq => \*(Aqpermessage\-deflate\*(Aq}) \& \->send_ok(\*(Aqy\*(Aq x 50000) \& \->message_ok \& \->message_is(\*(Aqz\*(Aq x 50000) \& \->finish_ok; .Ve .SH "SEE ALSO" .IX Header "SEE ALSO" Mojolicious, Mojolicious::Guides, .