Newer Older
883 lines | 24.36kb
add files
Yuki Kimoto authored on 2014-03-26
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