add files
|
1 |
package Test::Mojo; |
2 |
use Mojo::Base -base; |
|
3 | ||
4 |
# "Amy: He knows when you are sleeping. |
|
5 |
# Professor: He knows when you're on the can. |
|
6 |
# Leela: He'll hunt you down and blast your ass from here to Pakistan. |
|
7 |
# Zoidberg: Oh. |
|
8 |
# Hermes: You'd better not breathe, you'd better not move. |
|
9 |
# Bender: You're better off dead, I'm telling you, dude. |
|
10 |
# Fry: Santa Claus is gunning you down!" |
|
11 |
use Mojo::IOLoop; |
|
12 |
use Mojo::JSON; |
|
13 |
use Mojo::JSON::Pointer; |
|
14 |
use Mojo::Server; |
|
15 |
use Mojo::UserAgent; |
|
16 |
use Mojo::Util qw(decode encode); |
|
17 |
use Test::More (); |
|
18 | ||
19 |
has [qw(message tx)]; |
|
20 |
has ua => sub { Mojo::UserAgent->new->ioloop(Mojo::IOLoop->singleton) }; |
|
21 | ||
22 |
# Silent or loud tests |
|
23 |
$ENV{MOJO_LOG_LEVEL} ||= $ENV{HARNESS_IS_VERBOSE} ? 'debug' : 'fatal'; |
|
24 | ||
25 |
sub new { |
|
26 |
my $self = shift->SUPER::new; |
|
27 |
return $self unless my $app = shift; |
|
28 |
return $self->app(ref $app ? $app : Mojo::Server->new->build_app($app)); |
|
29 |
} |
|
30 | ||
31 |
sub app { |
|
32 |
my ($self, $app) = @_; |
|
33 |
return $self->ua->server->app unless $app; |
|
34 |
$self->ua->server->app($app); |
|
35 |
return $self; |
|
36 |
} |
|
37 | ||
38 |
sub content_is { |
|
39 |
my ($self, $value, $desc) = @_; |
|
40 |
$desc ||= 'exact match for content'; |
|
41 |
return $self->_test('is', $self->tx->res->text, $value, $desc); |
|
42 |
} |
|
43 | ||
44 |
sub content_isnt { |
|
45 |
my ($self, $value, $desc) = @_; |
|
46 |
$desc ||= 'no match for content'; |
|
47 |
return $self->_test('isnt', $self->tx->res->text, $value, $desc); |
|
48 |
} |
|
49 | ||
50 |
sub content_like { |
|
51 |
my ($self, $regex, $desc) = @_; |
|
52 |
$desc ||= 'content is similar'; |
|
53 |
return $self->_test('like', $self->tx->res->text, $regex, $desc); |
|
54 |
} |
|
55 | ||
56 |
sub content_unlike { |
|
57 |
my ($self, $regex, $desc) = @_; |
|
58 |
$desc ||= 'content is not similar'; |
|
59 |
return $self->_test('unlike', $self->tx->res->text, $regex, $desc); |
|
60 |
} |
|
61 | ||
62 |
sub content_type_is { |
|
63 |
my ($self, $type, $desc) = @_; |
|
64 |
$desc ||= "Content-Type: $type"; |
|
65 |
return $self->_test('is', $self->tx->res->headers->content_type, $type, |
|
66 |
$desc); |
|
67 |
} |
|
68 | ||
69 |
sub content_type_isnt { |
|
70 |
my ($self, $type, $desc) = @_; |
|
71 |
$desc ||= "not Content-Type: $type"; |
|
72 |
return $self->_test('isnt', $self->tx->res->headers->content_type, $type, |
|
73 |
$desc); |
|
74 |
} |
|
75 | ||
76 |
sub content_type_like { |
|
77 |
my ($self, $regex, $desc) = @_; |
|
78 |
$desc ||= 'Content-Type is similar'; |
|
79 |
return $self->_test('like', $self->tx->res->headers->content_type, $regex, |
|
80 |
$desc); |
|
81 |
} |
|
82 | ||
83 |
sub content_type_unlike { |
|
84 |
my ($self, $regex, $desc) = @_; |
|
85 |
$desc ||= 'Content-Type is not similar'; |
|
86 |
return $self->_test('unlike', $self->tx->res->headers->content_type, |
|
87 |
$regex, $desc); |
|
88 |
} |
|
89 | ||
90 |
sub delete_ok { shift->_request_ok(delete => @_) } |
|
91 | ||
92 |
sub element_exists { |
|
93 |
my ($self, $selector, $desc) = @_; |
|
94 |
$desc ||= encode 'UTF-8', qq{element for selector "$selector" exists}; |
|
95 |
return $self->_test('ok', $self->tx->res->dom->at($selector), $desc); |
|
96 |
} |
|
97 | ||
98 |
sub element_exists_not { |
|
99 |
my ($self, $selector, $desc) = @_; |
|
100 |
$desc ||= encode 'UTF-8', qq{no element for selector "$selector"}; |
|
101 |
return $self->_test('ok', !$self->tx->res->dom->at($selector), $desc); |
|
102 |
} |
|
103 | ||
104 |
sub finish_ok { |
|
105 |
my $self = shift; |
|
106 |
$self->tx->finish(@_); |
|
107 |
Mojo::IOLoop->one_tick while !$self->{finished}; |
|
108 |
return $self->_test('ok', 1, 'closed WebSocket'); |
|
109 |
} |
|
110 | ||
111 |
sub finished_ok { |
|
112 |
my ($self, $code) = @_; |
|
113 |
Mojo::IOLoop->one_tick while !$self->{finished}; |
|
114 |
Test::More::diag "WebSocket closed with status $self->{finished}[0]" |
|
115 |
unless my $ok = grep { $self->{finished}[0] == $_ } $code, 1006; |
|
116 |
return $self->_test('ok', $ok, "WebSocket closed with status $code"); |
|
117 |
} |
|
118 | ||
119 |
sub get_ok { shift->_request_ok(get => @_) } |
|
120 |
sub head_ok { shift->_request_ok(head => @_) } |
|
121 | ||
122 |
sub header_is { |
|
123 |
my ($self, $name, $value, $desc) = @_; |
|
124 |
$desc ||= "$name: " . ($value ? $value : ''); |
|
125 |
return $self->_test('is', scalar $self->tx->res->headers->header($name), |
|
126 |
$value, $desc); |
|
127 |
} |
|
128 | ||
129 |
sub header_isnt { |
|
130 |
my ($self, $name, $value, $desc) = @_; |
|
131 |
$desc ||= "not $name: " . ($value ? $value : ''); |
|
132 |
return $self->_test('isnt', scalar $self->tx->res->headers->header($name), |
|
133 |
$value, $desc); |
|
134 |
} |
|
135 | ||
136 |
sub header_like { |
|
137 |
my ($self, $name, $regex, $desc) = @_; |
|
138 |
return $self->_test('like', scalar $self->tx->res->headers->header($name), |
|
139 |
$regex, $desc || "$name is similar"); |
|
140 |
} |
|
141 | ||
142 |
sub header_unlike { |
|
143 |
my ($self, $name, $regex, $desc) = @_; |
|
144 |
return $self->_test('unlike', |
|
145 |
scalar $self->tx->res->headers->header($name) // '', |
|
146 |
$regex, $desc || "$name is not similar"); |
|
147 |
} |
|
148 | ||
149 |
sub json_has { |
|
150 |
my ($self, $p, $desc) = @_; |
|
151 |
$desc ||= qq{has value for JSON Pointer "$p"}; |
|
152 |
return $self->_test('ok', |
|
153 |
!!Mojo::JSON::Pointer->new->contains($self->tx->res->json, $p), $desc); |
|
154 |
} |
|
155 | ||
156 |
sub json_hasnt { |
|
157 |
my ($self, $p, $desc) = @_; |
|
158 |
$desc ||= qq{has no value for JSON Pointer "$p"}; |
|
159 |
return $self->_test('ok', |
|
160 |
!Mojo::JSON::Pointer->new->contains($self->tx->res->json, $p), $desc); |
|
161 |
} |
|
162 | ||
163 |
sub json_is { |
|
164 |
my $self = shift; |
|
165 |
my ($p, $data) = ref $_[0] ? ('', shift) : (shift, shift); |
|
166 |
my $desc = shift || qq{exact match for JSON Pointer "$p"}; |
|
167 |
return $self->_test('is_deeply', $self->tx->res->json($p), $data, $desc); |
|
168 |
} |
|
169 | ||
170 |
sub json_message_has { |
|
171 |
my ($self, $p, $desc) = @_; |
|
172 |
$desc ||= qq{has value for JSON Pointer "$p"}; |
|
173 |
return $self->_test('ok', $self->_json(contains => $p), $desc); |
|
174 |
} |
|
175 | ||
176 |
sub json_message_hasnt { |
|
177 |
my ($self, $p, $desc) = @_; |
|
178 |
$desc ||= qq{has no value for JSON Pointer "$p"}; |
|
179 |
return $self->_test('ok', !$self->_json(contains => $p), $desc); |
|
180 |
} |
|
181 | ||
182 |
sub json_message_is { |
|
183 |
my $self = shift; |
|
184 |
my ($p, $data) = ref $_[0] ? ('', shift) : (shift, shift); |
|
185 |
my $desc = shift || qq{exact match for JSON Pointer "$p"}; |
|
186 |
return $self->_test('is_deeply', $self->_json(get => $p), $data, $desc); |
|
187 |
} |
|
188 | ||
189 |
sub message_is { |
|
190 |
my ($self, $value, $desc) = @_; |
|
191 |
return $self->_message('is', $value, $desc || 'exact match for message'); |
|
192 |
} |
|
193 | ||
194 |
sub message_isnt { |
|
195 |
my ($self, $value, $desc) = @_; |
|
196 |
return $self->_message('isnt', $value, $desc || 'no match for message'); |
|
197 |
} |
|
198 | ||
199 |
sub message_like { |
|
200 |
my ($self, $regex, $desc) = @_; |
|
201 |
return $self->_message('like', $regex, $desc || 'message is similar'); |
|
202 |
} |
|
203 | ||
204 |
sub message_ok { |
|
205 |
my ($self, $desc) = @_; |
|
206 |
return $self->_test('ok', !!$self->_wait, $desc || 'message received'); |
|
207 |
} |
|
208 | ||
209 |
sub message_unlike { |
|
210 |
my ($self, $regex, $desc) = @_; |
|
211 |
return $self->_message('unlike', $regex, $desc || 'message is not similar'); |
|
212 |
} |
|
213 | ||
214 |
sub options_ok { shift->_request_ok(options => @_) } |
|
215 | ||
216 |
sub or { |
|
217 |
my ($self, $cb) = @_; |
|
218 |
$self->$cb unless $self->{latest}; |
|
219 |
return $self; |
|
220 |
} |
|
221 | ||
222 |
sub patch_ok { shift->_request_ok(patch => @_) } |
|
223 |
sub post_ok { shift->_request_ok(post => @_) } |
|
224 |
sub put_ok { shift->_request_ok(put => @_) } |
|
225 | ||
226 |
sub request_ok { |
|
227 |
my $self = shift; |
|
228 |
my $tx = $self->tx($self->ua->start(shift))->tx; |
|
229 |
return $self->_test('ok', $tx->is_finished, shift || 'perform request'); |
|
230 |
} |
|
231 | ||
232 |
sub reset_session { |
|
233 |
my $self = shift; |
|
234 |
if (my $jar = $self->ua->cookie_jar) { $jar->empty } |
|
235 |
return $self->tx(undef); |
|
236 |
} |
|
237 | ||
238 |
sub send_ok { |
|
239 |
my ($self, $msg, $desc) = @_; |
|
240 |
$self->tx->send($msg => sub { Mojo::IOLoop->stop }); |
|
241 |
Mojo::IOLoop->start; |
|
242 |
return $self->_test('ok', 1, $desc || 'send message'); |
|
243 |
} |
|
244 | ||
245 |
sub status_is { |
|
246 |
my ($self, $status, $desc) = @_; |
|
247 |
$desc ||= "$status " . $self->tx->res->new(code => $status)->default_message; |
|
248 |
return $self->_test('is', $self->tx->res->code, $status, $desc); |
|
249 |
} |
|
250 | ||
251 |
sub status_isnt { |
|
252 |
my ($self, $status, $desc) = @_; |
|
253 |
$desc |
|
254 |
||= "not $status " . $self->tx->res->new(code => $status)->default_message; |
|
255 |
return $self->_test('isnt', $self->tx->res->code, $status, $desc); |
|
256 |
} |
|
257 | ||
258 |
sub text_is { |
|
259 |
my ($self, $selector, $value, $desc) = @_; |
|
260 |
$desc ||= encode 'UTF-8', qq{exact match for selector "$selector"}; |
|
261 |
return $self->_test('is', $self->_text($selector), $value, $desc); |
|
262 |
} |
|
263 | ||
264 |
sub text_isnt { |
|
265 |
my ($self, $selector, $value, $desc) = @_; |
|
266 |
$desc ||= encode 'UTF-8', qq{no match for selector "$selector"}; |
|
267 |
return $self->_test('isnt', $self->_text($selector), $value, $desc); |
|
268 |
} |
|
269 | ||
270 |
sub text_like { |
|
271 |
my ($self, $selector, $regex, $desc) = @_; |
|
272 |
$desc ||= encode 'UTF-8', qq{similar match for selector "$selector"}; |
|
273 |
return $self->_test('like', $self->_text($selector), $regex, $desc); |
|
274 |
} |
|
275 | ||
276 |
sub text_unlike { |
|
277 |
my ($self, $selector, $regex, $desc) = @_; |
|
278 |
$desc ||= encode 'UTF-8', qq{no similar match for selector "$selector"}; |
|
279 |
return $self->_test('unlike', $self->_text($selector), $regex, $desc); |
|
280 |
} |
|
281 | ||
282 |
sub websocket_ok { |
|
283 |
my ($self, $url) = (shift, shift); |
|
284 | ||
285 |
# Establish WebSocket connection |
|
286 |
$self->{messages} = []; |
|
287 |
$self->{finished} = undef; |
|
288 |
$self->ua->websocket( |
|
289 |
$url => @_ => sub { |
|
290 |
my ($ua, $tx) = @_; |
|
291 |
$self->tx($tx); |
|
292 |
$tx->on(finish => sub { shift; $self->{finished} = [@_] }); |
|
293 |
$tx->on(binary => sub { push @{$self->{messages}}, [binary => pop] }); |
|
294 |
$tx->on(text => sub { push @{$self->{messages}}, [text => pop] }); |
|
295 |
Mojo::IOLoop->stop; |
|
296 |
} |
|
297 |
); |
|
298 |
Mojo::IOLoop->start; |
|
299 | ||
300 |
my $desc = encode 'UTF-8', "WebSocket $url"; |
|
301 |
return $self->_test('ok', $self->tx->res->code eq 101, $desc); |
|
302 |
} |
|
303 | ||
304 |
sub _json { |
|
305 |
my ($self, $method, $p) = @_; |
|
306 |
return Mojo::JSON::Pointer->new->$method( |
|
307 |
Mojo::JSON->new->decode(@{$self->message}[1]), $p); |
|
308 |
} |
|
309 | ||
310 |
sub _message { |
|
311 |
my ($self, $name, $value, $desc) = @_; |
|
312 |
local $Test::Builder::Level = $Test::Builder::Level + 1; |
|
313 |
my ($type, $msg) = @{$self->message}; |
|
314 | ||
315 |
# Type check |
|
316 |
if (ref $value eq 'HASH') { |
|
317 |
my $expect = exists $value->{text} ? 'text' : 'binary'; |
|
318 |
$value = $value->{$expect}; |
|
319 |
$msg = '' unless $type eq $expect; |
|
320 |
} |
|
321 | ||
322 |
# Decode text frame if there is no type check |
|
323 |
else { $msg = decode 'UTF-8', $msg if $type eq 'text' } |
|
324 | ||
325 |
return $self->_test($name, $msg // '', $value, $desc); |
|
326 |
} |
|
327 | ||
328 |
sub _request_ok { |
|
329 |
my ($self, $method, $url) = (shift, shift, shift); |
|
330 | ||
331 |
# Perform request against application |
|
332 |
$self->tx($self->ua->$method($url, @_)); |
|
333 |
local $Test::Builder::Level = $Test::Builder::Level + 1; |
|
334 |
my ($err, $code) = $self->tx->error; |
|
335 |
Test::More::diag $err if !(my $ok = !$err || $code) && $err; |
|
336 |
return $self->_test('ok', $ok, encode('UTF-8', "@{[uc $method]} $url")); |
|
337 |
} |
|
338 | ||
339 |
sub _test { |
|
340 |
my ($self, $name, @args) = @_; |
|
341 |
local $Test::Builder::Level = $Test::Builder::Level + 2; |
|
342 |
$self->{latest} = Test::More->can($name)->(@args); |
|
343 |
return $self; |
|
344 |
} |
|
345 | ||
346 |
sub _text { |
|
347 |
return '' unless my $e = shift->tx->res->dom->at(shift); |
|
348 |
return $e->text; |
|
349 |
} |
|
350 | ||
351 |
sub _wait { |
|
352 |
my $self = shift; |
|
353 |
Mojo::IOLoop->one_tick while !$self->{finished} && !@{$self->{messages}}; |
|
354 |
return $self->message(shift @{$self->{messages}})->message; |
|
355 |
} |
|
356 | ||
357 |
1; |
|
358 | ||
359 |
=encoding utf8 |
|
360 | ||
361 |
=head1 NAME |
|
362 | ||
363 |
Test::Mojo - Testing Mojo! |
|
364 | ||
365 |
=head1 SYNOPSIS |
|
366 | ||
367 |
use Test::More; |
|
368 |
use Test::Mojo; |
|
369 | ||
370 |
my $t = Test::Mojo->new('MyApp'); |
|
371 | ||
372 |
# HTML/XML |
|
373 |
$t->get_ok('/welcome')->status_is(200)->text_is('div#message' => 'Hello!'); |
|
374 | ||
375 |
# JSON |
|
376 |
$t->post_ok('/search.json' => form => {q => 'Perl'}) |
|
377 |
->status_is(200) |
|
378 |
->header_is('Server' => 'Mojolicious (Perl)') |
|
379 |
->header_isnt('X-Bender' => 'Bite my shiny metal ass!') |
|
380 |
->json_is('/results/4/title' => 'Perl rocks!'); |
|
381 | ||
382 |
# WebSocket |
|
383 |
$t->websocket_ok('/echo') |
|
384 |
->send_ok('hello') |
|
385 |
->message_ok |
|
386 |
->message_is('echo: hello') |
|
387 |
->finish_ok; |
|
388 | ||
389 |
done_testing(); |
|
390 | ||
391 |
=head1 DESCRIPTION |
|
392 | ||
393 |
L<Test::Mojo> is a collection of testing helpers for everyone developing |
|
394 |
L<Mojo> and L<Mojolicious> applications. |
|
395 | ||
396 |
=head1 ATTRIBUTES |
|
397 | ||
398 |
L<Test::Mojo> implements the following attributes. |
|
399 | ||
400 |
=head2 message |
|
401 | ||
402 |
my $msg = $t->message; |
|
403 |
$t = $t->message([text => $bytes]); |
|
404 | ||
405 |
Current WebSocket message. |
|
406 | ||
407 |
# Test custom message |
|
408 |
$t->message([binary => $bytes]) |
|
409 |
->json_message_has('/foo/bar') |
|
410 |
->json_message_hasnt('/bar') |
|
411 |
->json_message_is('/foo/baz' => {yada => [1, 2, 3]}); |
|
412 | ||
413 |
=head2 tx |
|
414 | ||
415 |
my $tx = $t->tx; |
|
416 |
$t = $t->tx(Mojo::Transaction::HTTP->new); |
|
417 | ||
418 |
Current transaction, usually a L<Mojo::Transaction::HTTP> object. |
|
419 | ||
420 |
# More specific tests |
|
421 |
is $t->tx->res->json->{foo}, 'bar', 'right value'; |
|
422 |
ok $t->tx->res->content->is_multipart, 'multipart content'; |
|
423 | ||
424 |
# Test custom transactions |
|
425 |
$t->tx($t->tx->previous)->status_is(302)->header_like(Location => qr/foo/); |
|
426 | ||
427 |
=head2 ua |
|
428 | ||
429 |
my $ua = $t->ua; |
|
430 |
$t = $t->ua(Mojo::UserAgent->new); |
|
431 | ||
432 |
User agent used for testing, defaults to a L<Mojo::UserAgent> object. |
|
433 | ||
434 |
# Allow redirects |
|
435 |
$t->ua->max_redirects(10); |
|
436 | ||
437 |
# Use absolute URL for request with Basic authentication |
|
438 |
my $url = $t->ua->server->url->userinfo('sri:secr3t')->path('/secrets.json'); |
|
439 |
$t->post_ok($url => json => {limit => 10}) |
|
440 |
->status_is(200) |
|
441 |
->json_is('/1/content', 'Mojo rocks!'); |
|
442 | ||
443 |
# Customize all transactions (including followed redirects) |
|
444 |
$t->ua->on(start => sub { |
|
445 |
my ($ua, $tx) = @_; |
|
446 |
$tx->req->headers->accept_language('en-US'); |
|
447 |
}); |
|
448 | ||
449 |
=head1 METHODS |
|
450 | ||
451 |
L<Test::Mojo> inherits all methods from L<Mojo::Base> and implements the |
|
452 |
following new ones. |
|
453 | ||
454 |
=head2 new |
|
455 | ||
456 |
my $t = Test::Mojo->new; |
|
457 |
my $t = Test::Mojo->new('MyApp'); |
|
458 |
my $t = Test::Mojo->new(MyApp->new); |
|
459 | ||
460 |
Construct a new L<Test::Mojo> object. |
|
461 | ||
462 |
=head2 app |
|
463 | ||
464 |
my $app = $t->app; |
|
465 |
$t = $t->app(MyApp->new); |
|
466 | ||
467 |
Access application with L<Mojo::UserAgent/"app">. |
|
468 | ||
469 |
# Change log level |
|
470 |
$t->app->log->level('fatal'); |
|
471 | ||
472 |
# Test application directly |
|
473 |
is $t->app->defaults->{foo}, 'bar', 'right value'; |
|
474 |
ok $t->app->routes->find('echo')->is_websocket, 'WebSocket route'; |
|
475 | ||
476 |
# Change application behavior |
|
477 |
$t->app->hook(before_dispatch => sub { |
|
478 |
my $c = shift; |
|
479 |
$c->render(text => 'This request did not reach the router.') |
|
480 |
if $c->req->url->path->contains('/user'); |
|
481 |
}); |
|
482 | ||
483 |
# Extract additional information |
|
484 |
my $stash; |
|
485 |
$t->app->hook(after_dispatch => sub { $stash = shift->stash }); |
|
486 | ||
487 |
=head2 content_is |
|
488 | ||
489 |
$t = $t->content_is('working!'); |
|
490 |
$t = $t->content_is('working!', 'right content'); |
|
491 | ||
492 |
Check response content for exact match after retrieving it from |
|
493 |
L<Mojo::Message/"text">. |
|
494 | ||
495 |
=head2 content_isnt |
|
496 | ||
497 |
$t = $t->content_isnt('working!'); |
|
498 |
$t = $t->content_isnt('working!', 'different content'); |
|
499 | ||
500 |
Opposite of L</"content_is">. |
|
501 | ||
502 |
=head2 content_like |
|
503 | ||
504 |
$t = $t->content_like(qr/working!/); |
|
505 |
$t = $t->content_like(qr/working!/, 'right content'); |
|
506 | ||
507 |
Check response content for similar match after retrieving it from |
|
508 |
L<Mojo::Message/"text">. |
|
509 | ||
510 |
=head2 content_unlike |
|
511 | ||
512 |
$t = $t->content_unlike(qr/working!/); |
|
513 |
$t = $t->content_unlike(qr/working!/, 'different content'); |
|
514 | ||
515 |
Opposite of L</"content_like">. |
|
516 | ||
517 |
=head2 content_type_is |
|
518 | ||
519 |
$t = $t->content_type_is('text/html'); |
|
520 |
$t = $t->content_type_is('text/html', 'right content type'); |
|
521 | ||
522 |
Check response C<Content-Type> header for exact match. |
|
523 | ||
524 |
=head2 content_type_isnt |
|
525 | ||
526 |
$t = $t->content_type_isnt('text/html'); |
|
527 |
$t = $t->content_type_isnt('text/html', 'different content type'); |
|
528 | ||
529 |
Opposite of L</"content_type_is">. |
|
530 | ||
531 |
=head2 content_type_like |
|
532 | ||
533 |
$t = $t->content_type_like(qr/text/); |
|
534 |
$t = $t->content_type_like(qr/text/, 'right content type'); |
|
535 | ||
536 |
Check response C<Content-Type> header for similar match. |
|
537 | ||
538 |
=head2 content_type_unlike |
|
539 | ||
540 |
$t = $t->content_type_unlike(qr/text/); |
|
541 |
$t = $t->content_type_unlike(qr/text/, 'different content type'); |
|
542 | ||
543 |
Opposite of L</"content_type_like">. |
|
544 | ||
545 |
=head2 delete_ok |
|
546 | ||
547 |
$t = $t->delete_ok('/foo'); |
|
548 |
$t = $t->delete_ok('/foo' => {DNT => 1} => 'Hi!'); |
|
549 |
$t = $t->delete_ok('/foo' => {DNT => 1} => form => {a => 'b'}); |
|
550 |
$t = $t->delete_ok('/foo' => {DNT => 1} => json => {a => 'b'}); |
|
551 | ||
552 |
Perform a DELETE request and check for transport errors, takes the same |
|
553 |
arguments as L<Mojo::UserAgent/"delete">, except for the callback. |
|
554 | ||
555 |
=head2 element_exists |
|
556 | ||
557 |
$t = $t->element_exists('div.foo[x=y]'); |
|
558 |
$t = $t->element_exists('html head title', 'has a title'); |
|
559 | ||
560 |
Checks for existence of the CSS selectors first matching HTML/XML element with |
|
561 |
L<Mojo::DOM>. |
|
562 | ||
563 |
=head2 element_exists_not |
|
564 | ||
565 |
$t = $t->element_exists_not('div.foo[x=y]'); |
|
566 |
$t = $t->element_exists_not('html head title', 'has no title'); |
|
567 | ||
568 |
Opposite of L</"element_exists">. |
|
569 | ||
570 |
=head2 finish_ok |
|
571 | ||
572 |
$t = $t->finish_ok; |
|
573 |
$t = $t->finish_ok(1000); |
|
574 |
$t = $t->finish_ok(1003 => 'Cannot accept data!'); |
|
575 | ||
576 |
Close WebSocket connection gracefully. |
|
577 | ||
578 |
=head2 finished_ok |
|
579 | ||
580 |
$t = $t->finished_ok(1000); |
|
581 | ||
582 |
Wait for WebSocket connection to be closed gracefully and check status. |
|
583 | ||
584 |
=head2 get_ok |
|
585 | ||
586 |
$t = $t->get_ok('/foo'); |
|
587 |
$t = $t->get_ok('/foo' => {DNT => 1} => 'Hi!'); |
|
588 |
$t = $t->get_ok('/foo' => {DNT => 1} => form => {a => 'b'}); |
|
589 |
$t = $t->get_ok('/foo' => {DNT => 1} => json => {a => 'b'}); |
|
590 | ||
591 |
Perform a GET request and check for transport errors, takes the same |
|
592 |
arguments as L<Mojo::UserAgent/"get">, except for the callback. |
|
593 | ||
594 |
=head2 head_ok |
|
595 | ||
596 |
$t = $t->head_ok('/foo'); |
|
597 |
$t = $t->head_ok('/foo' => {DNT => 1} => 'Hi!'); |
|
598 |
$t = $t->head_ok('/foo' => {DNT => 1} => form => {a => 'b'}); |
|
599 |
$t = $t->head_ok('/foo' => {DNT => 1} => json => {a => 'b'}); |
|
600 | ||
601 |
Perform a HEAD request and check for transport errors, takes the same |
|
602 |
arguments as L<Mojo::UserAgent/"head">, except for the callback. |
|
603 | ||
604 |
=head2 header_is |
|
605 | ||
606 |
$t = $t->header_is(Expect => 'fun'); |
|
607 |
$t = $t->header_is(Expect => 'fun', 'right header'); |
|
608 | ||
609 |
Check response header for exact match. |
|
610 | ||
611 |
=head2 header_isnt |
|
612 | ||
613 |
$t = $t->header_isnt(Expect => 'fun'); |
|
614 |
$t = $t->header_isnt(Expect => 'fun', 'different header'); |
|
615 | ||
616 |
Opposite of L</"header_is">. |
|
617 | ||
618 |
=head2 header_like |
|
619 | ||
620 |
$t = $t->header_like(Expect => qr/fun/); |
|
621 |
$t = $t->header_like(Expect => qr/fun/, 'right header'); |
|
622 | ||
623 |
Check response header for similar match. |
|
624 | ||
625 |
=head2 header_unlike |
|
626 | ||
627 |
$t = $t->header_like(Expect => qr/fun/); |
|
628 |
$t = $t->header_like(Expect => qr/fun/, 'different header'); |
|
629 | ||
630 |
Opposite of L</"header_like">. |
|
631 | ||
632 |
=head2 json_has |
|
633 | ||
634 |
$t = $t->json_has('/foo'); |
|
635 |
$t = $t->json_has('/minibar', 'has a minibar'); |
|
636 | ||
637 |
Check if JSON response contains a value that can be identified using the given |
|
638 |
JSON Pointer with L<Mojo::JSON::Pointer>. |
|
639 | ||
640 |
=head2 json_hasnt |
|
641 | ||
642 |
$t = $t->json_hasnt('/foo'); |
|
643 |
$t = $t->json_hasnt('/minibar', 'no minibar'); |
|
644 | ||
645 |
Opposite of L</"json_has">. |
|
646 | ||
647 |
=head2 json_is |
|
648 | ||
649 |
$t = $t->json_is({foo => [1, 2, 3]}); |
|
650 |
$t = $t->json_is({foo => [1, 2, 3]}, 'right content'); |
|
651 |
$t = $t->json_is('/foo' => [1, 2, 3]); |
|
652 |
$t = $t->json_is('/foo/1' => 2, 'right value'); |
|
653 | ||
654 |
Check the value extracted from JSON response using the given JSON Pointer with |
|
655 |
L<Mojo::JSON::Pointer>, which defaults to the root value if it is omitted. |
|
656 | ||
657 |
=head2 json_message_has |
|
658 | ||
659 |
$t = $t->json_message_has('/foo'); |
|
660 |
$t = $t->json_message_has('/minibar', 'has a minibar'); |
|
661 | ||
662 |
Check if JSON WebSocket message contains a value that can be identified using |
|
663 |
the given JSON Pointer with L<Mojo::JSON::Pointer>. |
|
664 | ||
665 |
=head2 json_message_hasnt |
|
666 | ||
667 |
$t = $t->json_message_hasnt('/foo'); |
|
668 |
$t = $t->json_message_hasnt('/minibar', 'no minibar'); |
|
669 | ||
670 |
Opposite of L</"json_message_has">. |
|
671 | ||
672 |
=head2 json_message_is |
|
673 | ||
674 |
$t = $t->json_message_is({foo => [1, 2, 3]}); |
|
675 |
$t = $t->json_message_is({foo => [1, 2, 3]}, 'right content'); |
|
676 |
$t = $t->json_message_is('/foo' => [1, 2, 3]); |
|
677 |
$t = $t->json_message_is('/foo/1' => 2, 'right value'); |
|
678 | ||
679 |
Check the value extracted from JSON WebSocket message using the given JSON |
|
680 |
Pointer with L<Mojo::JSON::Pointer>, which defaults to the root value if it is |
|
681 |
omitted. |
|
682 | ||
683 |
=head2 message_is |
|
684 | ||
685 |
$t = $t->message_is({binary => $bytes}); |
|
686 |
$t = $t->message_is({text => $bytes}); |
|
687 |
$t = $t->message_is('working!'); |
|
688 |
$t = $t->message_is('working!', 'right message'); |
|
689 | ||
690 |
Check WebSocket message for exact match. |
|
691 | ||
692 |
=head2 message_isnt |
|
693 | ||
694 |
$t = $t->message_isnt({binary => $bytes}); |
|
695 |
$t = $t->message_isnt({text => $bytes}); |
|
696 |
$t = $t->message_isnt('working!'); |
|
697 |
$t = $t->message_isnt('working!', 'different message'); |
|
698 | ||
699 |
Opposite of L</"message_is">. |
|
700 | ||
701 |
=head2 message_like |
|
702 | ||
703 |
$t = $t->message_like({binary => qr/$bytes/}); |
|
704 |
$t = $t->message_like({text => qr/$bytes/}); |
|
705 |
$t = $t->message_like(qr/working!/); |
|
706 |
$t = $t->message_like(qr/working!/, 'right message'); |
|
707 | ||
708 |
Check WebSocket message for similar match. |
|
709 | ||
710 |
=head2 message_ok |
|
711 | ||
712 |
$t = $t->message_ok; |
|
713 |
$t = $t->message_ok('got a message'); |
|
714 | ||
715 |
Wait for next WebSocket message to arrive. |
|
716 | ||
717 |
# Wait for message and perform multiple tests on it |
|
718 |
$t->websocket_ok('/time') |
|
719 |
->message_ok |
|
720 |
->message_like(qr/\d+/) |
|
721 |
->message_unlike(qr/\w+/) |
|
722 |
->finish_ok; |
|
723 | ||
724 |
=head2 message_unlike |
|
725 | ||
726 |
$t = $t->message_unlike({binary => qr/$bytes/}); |
|
727 |
$t = $t->message_unlike({text => qr/$bytes/}); |
|
728 |
$t = $t->message_unlike(qr/working!/); |
|
729 |
$t = $t->message_unlike(qr/working!/, 'different message'); |
|
730 | ||
731 |
Opposite of L</"message_like">. |
|
732 | ||
733 |
=head2 options_ok |
|
734 | ||
735 |
$t = $t->options_ok('/foo'); |
|
736 |
$t = $t->options_ok('/foo' => {DNT => 1} => 'Hi!'); |
|
737 |
$t = $t->options_ok('/foo' => {DNT => 1} => form => {a => 'b'}); |
|
738 |
$t = $t->options_ok('/foo' => {DNT => 1} => json => {a => 'b'}); |
|
739 | ||
740 |
Perform a OPTIONS request and check for transport errors, takes the same |
|
741 |
arguments as L<Mojo::UserAgent/"options">, except for the callback. |
|
742 | ||
743 |
=head2 or |
|
744 | ||
745 |
$t = $t->or(sub {...}); |
|
746 | ||
747 |
Invoke callback if previous test failed. |
|
748 | ||
749 |
# Diagnostics |
|
750 |
$t->get_ok('/bad')->or(sub { diag 'Must have been Glen!' }) |
|
751 |
->status_is(200)->or(sub { diag $t->tx->res->dom->at('title')->text }); |
|
752 | ||
753 |
=head2 patch_ok |
|
754 | ||
755 |
$t = $t->patch_ok('/foo'); |
|
756 |
$t = $t->patch_ok('/foo' => {DNT => 1} => 'Hi!'); |
|
757 |
$t = $t->patch_ok('/foo' => {DNT => 1} => form => {a => 'b'}); |
|
758 |
$t = $t->patch_ok('/foo' => {DNT => 1} => json => {a => 'b'}); |
|
759 | ||
760 |
Perform a PATCH request and check for transport errors, takes the same |
|
761 |
arguments as L<Mojo::UserAgent/"patch">, except for the callback. |
|
762 | ||
763 |
=head2 post_ok |
|
764 | ||
765 |
$t = $t->post_ok('/foo'); |
|
766 |
$t = $t->post_ok('/foo' => {DNT => 1} => 'Hi!'); |
|
767 |
$t = $t->post_ok('/foo' => {DNT => 1} => form => {a => 'b'}); |
|
768 |
$t = $t->post_ok('/foo' => {DNT => 1} => json => {a => 'b'}); |
|
769 | ||
770 |
Perform a POST request and check for transport errors, takes the same |
|
771 |
arguments as L<Mojo::UserAgent/"post">, except for the callback. |
|
772 | ||
773 |
# Test file upload |
|
774 |
$t->post_ok('/upload' => form => {foo => {content => 'bar'}}) |
|
775 |
->status_is(200); |
|
776 | ||
777 |
# Test JSON API |
|
778 |
$t->post_json_ok('/hello.json' => json => {hello => 'world'}) |
|
779 |
->status_is(200) |
|
780 |
->json_is({bye => 'world'}); |
|
781 | ||
782 |
=head2 put_ok |
|
783 | ||
784 |
$t = $t->put_ok('/foo'); |
|
785 |
$t = $t->put_ok('/foo' => {DNT => 1} => 'Hi!'); |
|
786 |
$t = $t->put_ok('/foo' => {DNT => 1} => form => {a => 'b'}); |
|
787 |
$t = $t->put_ok('/foo' => {DNT => 1} => json => {a => 'b'}); |
|
788 | ||
789 |
Perform a PUT request and check for transport errors, takes the same |
|
790 |
arguments as L<Mojo::UserAgent/"put">, except for the callback. |
|
791 | ||
792 |
=head2 request_ok |
|
793 | ||
794 |
$t = $t->request_ok(Mojo::Transaction::HTTP->new); |
|
795 |
$t = $t->request_ok(Mojo::Transaction::HTTP->new, 'request successful'); |
|
796 | ||
797 |
Perform request and check for transport errors. |
|
798 | ||
799 |
# Request with custom method |
|
800 |
my $tx = $t->ua->build_tx(FOO => '/test.json' => json => {foo => 1}); |
|
801 |
$t->request_ok($tx)->status_is(200)->json_is({success => 1}); |
|
802 | ||
803 |
=head2 reset_session |
|
804 | ||
805 |
$t = $t->reset_session; |
|
806 | ||
807 |
Reset user agent session. |
|
808 | ||
809 |
=head2 send_ok |
|
810 | ||
811 |
$t = $t->send_ok({binary => $bytes}); |
|
812 |
$t = $t->send_ok({text => $bytes}); |
|
813 |
$t = $t->send_ok({json => {test => [1, 2, 3]}}); |
|
814 |
$t = $t->send_ok([$fin, $rsv1, $rsv2, $rsv3, $op, $payload]); |
|
815 |
$t = $t->send_ok($chars); |
|
816 |
$t = $t->send_ok($chars, 'sent successfully'); |
|
817 | ||
818 |
Send message or frame via WebSocket. |
|
819 | ||
820 |
# Send JSON object as "Text" message |
|
821 |
$t->websocket_ok('/echo.json') |
|
822 |
->send_ok({json => {test => 'I ♥ Mojolicious!'}}) |
|
823 |
->message_ok |
|
824 |
->json_message_is('/test' => 'I ♥ Mojolicious!') |
|
825 |
->finish_ok; |
|
826 | ||
827 |
=head2 status_is |
|
828 | ||
829 |
$t = $t->status_is(200); |
|
830 |
$t = $t->status_is(200, 'right status'); |
|
831 | ||
832 |
Check response status for exact match. |
|
833 | ||
834 |
=head2 status_isnt |
|
835 | ||
836 |
$t = $t->status_isnt(200); |
|
837 |
$t = $t->status_isnt(200, 'different status'); |
|
838 | ||
839 |
Opposite of L</"status_is">. |
|
840 | ||
841 |
=head2 text_is |
|
842 | ||
843 |
$t = $t->text_is('div.foo[x=y]' => 'Hello!'); |
|
844 |
$t = $t->text_is('html head title' => 'Hello!', 'right title'); |
|
845 | ||
846 |
Checks text content of the CSS selectors first matching HTML/XML element for |
|
847 |
exact match with L<Mojo::DOM>. |
|
848 | ||
849 |
=head2 text_isnt |
|
850 | ||
851 |
$t = $t->text_isnt('div.foo[x=y]' => 'Hello!'); |
|
852 |
$t = $t->text_isnt('html head title' => 'Hello!', 'different title'); |
|
853 | ||
854 |
Opposite of L</"text_is">. |
|
855 | ||
856 |
=head2 text_like |
|
857 | ||
858 |
$t = $t->text_like('div.foo[x=y]' => qr/Hello/); |
|
859 |
$t = $t->text_like('html head title' => qr/Hello/, 'right title'); |
|
860 | ||
861 |
Checks text content of the CSS selectors first matching HTML/XML element for |
|
862 |
similar match with L<Mojo::DOM>. |
|
863 | ||
864 |
=head2 text_unlike |
|
865 | ||
866 |
$t = $t->text_unlike('div.foo[x=y]' => qr/Hello/); |
|
867 |
$t = $t->text_unlike('html head title' => qr/Hello/, 'different title'); |
|
868 | ||
869 |
Opposite of L</"text_like">. |
|
870 | ||
871 |
=head2 websocket_ok |
|
872 | ||
873 |
$t = $t->websocket_ok('/echo'); |
|
874 |
$t = $t->websocket_ok('/echo' => {DNT => 1} => ['v1.proto']); |
|
875 | ||
876 |
Open a WebSocket connection with transparent handshake, takes the same |
|
877 |
arguments as L<Mojo::UserAgent/"websocket">, except for the callback. |
|
878 | ||
879 |
=head1 SEE ALSO |
|
880 | ||
881 |
L<Mojolicious>, L<Mojolicious::Guides>, L<http://mojolicio.us>. |
|
882 | ||
883 |
=cut |