File Coverage

blib/lib/Catalyst/Test.pm
Criterion Covered Total %
statement 128 152 84.2
branch 34 58 58.6
condition 7 14 50.0
subroutine 32 35 91.4
pod n/a
total 201 259 77.6


line stmt bran cond sub pod time code
1              
2             use strict;
3 126     169   5317345 use warnings;
  126         1105  
  126         3243  
4 126     126   613 use Test::More ();
  126         210  
  126         2772  
5 126     126   3133  
  126         266691  
  126         2067  
6             use Plack::Test;
7 126     126   46847 use Catalyst::Exception;
  126         110855  
  126         5650  
8 126     126   31310 use Catalyst::Utils;
  126         467  
  126         4112  
9 126     126   45067 use Class::Load qw(load_class is_class_loaded);
  126         394  
  126         3796  
10 126     126   711 use Sub::Exporter;
  126         216  
  126         5308  
11 126     126   710 use Moose::Util 'find_meta';
  126         223  
  126         884  
12 126     126   22966 use Carp 'croak', 'carp';
  126         279  
  126         663  
13 126     126   25591  
  126         260  
  126         205713  
14             my ($self, $args) = @_;
15              
16 127     127   261 return sub { _remote_request(@_) }
17             if $args->{remote};
18 0     0   0  
19 127 50       379 my $class = $args->{class};
20              
21 127         303 # Here we should be failing right away, but for some stupid backcompat thing
22             # I don't quite remember we fail lazily here. Needs a proper deprecation and
23             # then removal.
24             return sub { croak "Must specify a test app: use Catalyst::Test 'TestApp'" }
25             unless $class;
26 2     2   1179  
27 127 100       357 load_class($class) unless is_class_loaded($class);
28             $class->import;
29 125 100       3124  
30 125         9908 return sub { _local_request( $class, @_ ) };
31             }
32 125     905   1035  
  905         705790  
33             my ($self, $args) = @_;
34             my $request = $args->{request};
35              
36 127     127   397 return sub { $request->(@_)->content };
37 127         330 }
38             my ($self, $args) = @_;
39 127     43   496 my ($class, $request) = @{ $args }{qw(class request)};
  43         36584  
40              
41             return sub {
42 127     127   388 my $me = ref $self || $self;
43 127         319  
  127         397  
44             # fail if ctx_request is being used against a remote server
45             Catalyst::Exception->throw("$me only works with local requests, not remote")
46 79   33 79   87408 if $ENV{CATALYST_SERVER};
47              
48             # check explicitly for the class here, or the Cat->meta call will blow
49             # up in our face
50 79 50       277 Catalyst::Exception->throw("Must specify a test app: use Catalyst::Test 'TestApp'") unless $class;
51              
52             # place holder for $c after the request finishes; reset every time
53             # requests are done.
54 79 100       205 my $ctx_closed_over;
55              
56             # hook into 'dispatch' -- the function gets called after all plugins
57             # have done their work, and it's an easy place to capture $c.
58 78         115 my $meta = find_meta($class);
59             $meta->make_mutable;
60             $meta->add_after_method_modifier( "dispatch", sub {
61             $ctx_closed_over = shift;
62 78         250 });
63 78         1226 $meta->make_immutable( replace_constructor => 1 );
64             Class::C3::reinitialize(); # Fixes RT#46459, I've failed to write a test for how/why, but it does.
65 954     954   5317  
66 78         13577 # do the request; C::T::request will know about the class name, and
67 78         38259 # we've already stopped it from doing remote requests above.
68 78         26567 my $res = $args->{request}->( @_ );
69              
70             # Make sure not to leave a reference $ctx hanging around.
71             # This means that the context will go out of scope as soon as the
72 78         381 # caller disposes of it, rather than waiting till the next time
73             # that ctx_request is called. This can be important if your $ctx
74             # ends up with a reference to a shared resource or lock (for example)
75             # which you want to clean up in test teardown - if the $ctx is still
76             # closed over then you're stuffed...
77             my $ctx = $ctx_closed_over;
78             undef $ctx_closed_over;
79              
80             return ( $res, $ctx );
81 78         137 };
82 78         140 }
83              
84 78         337 my $build_exports = sub {
85 127         750 my ($self, $meth, $args, $defaults) = @_;
86             my $class = $args->{class};
87              
88             my $request = $self->_build_request_export({
89             class => $class,
90             remote => $ENV{CATALYST_SERVER},
91             });
92              
93             my $get = $self->_build_get_export({ request => $request });
94              
95             my $ctx_request = $self->_build_ctx_request_export({
96             class => $class,
97             request => $request,
98             });
99              
100             return {
101             request => $request,
102             get => $get,
103             ctx_request => $ctx_request,
104             content_like => sub {
105             my $action = shift;
106             return Test::More->builder->like($get->($action),@_);
107             },
108             action_ok => sub {
109 9     9   4648 my $action = shift;
110 9         70 my $meth = $request->($action)->request->method;
111             my @args = @_ ? @_ : ("$meth $action returns successfully");
112             return Test::More->builder->ok($request->($action)->is_success,@args);
113 3     3   1462 },
114 3         9 action_redirect => sub {
115 3 100       87 my $action = shift;
116 3         25 my $meth = $request->($action)->request->method;
117             my @args = @_ ? @_ : ("$meth $action returns a redirect");
118             return Test::More->builder->ok($request->($action)->is_redirect,@args);
119 1     1   284 },
120 1         4 action_notfound => sub {
121 1 50       29 my $action = shift;
122 1         7 my $meth = $request->($action)->request->method;
123             my @args = @_ ? @_ : ("$meth $action returns a 404");
124             return Test::More->builder->is_eq($request->($action)->code,404,@args);
125 2     2   973 },
126 2         10 contenttype_is => sub {
127 2 100       61 my $action = shift;
128 2         15 my $res = $request->($action);
129             return Test::More->builder->is_eq(scalar($res->content_type),@_);
130             },
131 1     1   294 };
132 1         5 };
133 1         9  
134             our $default_host;
135              
136             {
137             my $import = Sub::Exporter::build_exporter({
138             groups => [ all => $build_exports ],
139             into_level => 1,
140             });
141              
142              
143             my ($self, $class, $opts) = @_;
144             Carp::carp(
145             qq{Importing Catalyst::Test without an application name is deprecated:\n
146             Instead of saying: use Catalyst::Test;
147             say: use Catalyst::Test (); # If you don't want to import a test app right now.
148 127     127   2292 or say: use Catalyst::Test 'MyApp'; # If you do want to import a test app.\n\n})
149 127 100       952 unless $class;
150             $import->($self, '-all' => { class => $class });
151             $opts = {} unless ref $opts eq 'HASH';
152             $default_host = $opts->{default_host} if exists $opts->{default_host};
153             return 1;
154             }
155 127         503 }
156 127 100       84439  
157 127 100       633 =head1 NAME
158 127         385546  
159             Catalyst::Test - Test Catalyst Applications
160              
161             =head1 SYNOPSIS
162              
163             # Helper
164             script/test.pl
165              
166             # Tests
167             use Catalyst::Test 'TestApp';
168             my $content = get('index.html'); # Content as string
169             my $response = request('index.html'); # HTTP::Response object
170             my($res, $c) = ctx_request('index.html'); # HTTP::Response & context object
171              
172             use HTTP::Request::Common;
173             my $response = request POST '/foo', [
174             bar => 'baz',
175             something => 'else'
176             ];
177              
178             # Run tests against a remote server
179             CATALYST_SERVER='http://localhost:3000/' prove -r -l lib/ t/
180              
181             use Catalyst::Test 'TestApp';
182             use Test::More tests => 1;
183              
184             ok( get('/foo') =~ /bar/ );
185              
186             # mock virtual hosts
187             use Catalyst::Test 'MyApp', { default_host => 'myapp.com' };
188             like( get('/whichhost'), qr/served by myapp.com/ );
189             like( get( '/whichhost', { host => 'yourapp.com' } ), qr/served by yourapp.com/ );
190             {
191             local $Catalyst::Test::default_host = 'otherapp.com';
192             like( get('/whichhost'), qr/served by otherapp.com/ );
193             }
194              
195             =head1 DESCRIPTION
196              
197             This module allows you to make requests to a Catalyst application either without
198             a server, by simulating the environment of an HTTP request using
199             L<HTTP::Request::AsCGI> or remotely if you define the CATALYST_SERVER
200             environment variable. This module also adds a few Catalyst-specific
201             testing methods as displayed in the method section.
202              
203             The L<get|/"$content = get( ... )"> and L<request|/"$res = request( ... );">
204             functions take either a URI or an L<HTTP::Request> object.
205              
206             =head1 INLINE TESTS WILL NO LONGER WORK
207              
208             While it used to be possible to inline a whole test app into a C<.t> file for
209             a distribution, this will no longer work.
210              
211             The convention is to place your L<Catalyst> test apps into C<t/lib> in your
212             distribution. E.g.: C<t/lib/TestApp.pm>, C<t/lib/TestApp/Controller/Root.pm>,
213             etc.. Multiple test apps can be used in this way.
214              
215             Then write your C<.t> files like so:
216              
217             use strict;
218             use warnings;
219             use FindBin '$Bin';
220             use lib "$Bin/lib";
221             use Test::More tests => 6;
222             use Catalyst::Test 'TestApp';
223              
224             =head1 METHODS
225              
226             =head2 $content = get( ... )
227              
228             Returns the content.
229              
230             my $content = get('foo/bar?test=1');
231              
232             Note that this method doesn't follow redirects, so to test for a
233             correctly redirecting page you'll need to use a combination of this
234             method and the L<request|/"$res = request( ... );"> method below:
235              
236             my $res = request('/'); # redirects to /y
237             warn $res->header('location');
238             use URI;
239             my $uri = URI->new($res->header('location'));
240             is ( $uri->path , '/y');
241             my $content = get($uri->path);
242              
243             Note also that the content is returned as raw bytes, without any attempt
244             to decode it into characters.
245              
246             =head2 $res = request( ... );
247              
248             Returns an L<HTTP::Response> object. Accepts an optional hashref for request
249             header configuration; currently only supports setting 'host' value.
250              
251             my $res = request('foo/bar?test=1');
252             my $virtual_res = request('foo/bar?test=1', {host => 'virtualhost.com'});
253              
254             Alternately, you can pass in an L<HTTP::Request::Common> object to set arbitrary
255             request headers.
256              
257             my $res = request(GET '/foo/bar',
258             X-Foo => 'Bar',
259             Authorization => 'Bearer JWT_HERE',
260             ...
261             );
262              
263             =head2 ($res, $c) = ctx_request( ... );
264              
265             Works exactly like L<request|/"$res = request( ... );">, except it also returns the Catalyst context object,
266             C<$c>. Note that this only works for local requests.
267              
268             =cut
269              
270             my $args = shift;
271              
272             my $request = Catalyst::Utils::request(shift);
273              
274             my %extra_env;
275             _customize_request($request, \%extra_env, @_);
276 906     906   1796 $args->{mangle_request}->($request) if $args->{mangle_request};
277              
278 906         3212 my $ret;
279             test_psgi
280 906         1654 %{ $args },
281 906         3048 app => sub { $args->{app}->({ %{ $_[0] }, %extra_env }) },
282 906 50       2238 client => sub {
283             my ($psgi_app) = @_;
284 906         1352 my $resp = $psgi_app->($request);
285             $args->{mangle_response}->($resp) if $args->{mangle_response};
286 906         7700 $ret = $resp;
287 906     906   691152 };
  906         12543  
288              
289 906     906   693475 return $ret;
290 906         2108 }
291 905 50       90448  
292 905         56120 my $class = shift;
293 906         1407  
294             return _request({
295 905         15947 app => ref($class) eq "CODE" ? $class : $class->_finalized_psgi_app,
296             mangle_response => sub {
297             my ($resp) = @_;
298              
299 906     906   1875 # HTML head parsing based on LWP::UserAgent
300             #
301             # This is because if you make a remote request with LWP, then the
302             # <BASE HREF="..."> from the returned HTML document will be used
303             # to fill in $res->base, as documented in HTTP::Response. We need
304 905     905   1985 # to support this in local test requests so that they work 'the same'.
305             #
306             # This is not just horrible and possibly broken, but also really
307             # doesn't belong here. Whoever wants this should be working on
308             # getting it into Plack::Test, or make a middleware out of it, or
309             # whatever. Seriously - horrible.
310              
311             if (!$resp->content_type || $resp->content_is_html) {
312             require HTML::HeadParser;
313              
314             my $parser = HTML::HeadParser->new();
315             $parser->xml_mode(1) if $resp->content_is_xhtml;
316             $parser->utf8_mode(1) if $] >= 5.008 && $HTML::Parser::VERSION >= 3.40;
317              
318 905 100 100     3184 $parser->parse( $resp->content );
319 702         60758 my $h = $parser->header;
320             for my $f ( $h->header_field_names ) {
321 702         110108 $resp->init_header( $f, [ $h->header($f) ] );
322 702 50       61691 }
323 702 50 33     22215 }
324             # Another horrible hack to make the response headers have a
325 702         1758 # 'status' field. This is for back-compat, but you should
326 702         23167 # call $resp->code instead!
327 702         4134 $resp->init_header('status', [ $resp->code ]);
328 26         1030 },
329             }, @_);
330             }
331              
332             my $agent;
333              
334 905         29979 require LWP::UserAgent;
335             local $Plack::Test::Impl = 'ExternalServer';
336 906 50       6717  
337             unless ($agent) {
338             $agent = LWP::UserAgent->new(
339             keep_alive => 1,
340             max_redirect => 0,
341             timeout => 60,
342 0     0   0  
343 0         0 # work around newer LWP max_redirect 0 bug
344             # http://rt.cpan.org/Ticket/Display.html?id=40260
345 0 0       0 requests_redirectable => [],
346 0         0 );
347              
348             $agent->env_proxy;
349             }
350              
351              
352             my $server = URI->new($ENV{CATALYST_SERVER});
353             if ( $server->path =~ m|^(.+)?/$| ) {
354             my $path = $1;
355             $server->path("$path") if $path; # need to be quoted
356 0         0 }
357              
358             return _request({
359             ua => $agent,
360 0         0 uri => $server,
361 0 0       0 mangle_request => sub {
362 0         0 my ($request) = @_;
363 0 0       0  
364             # the request path needs to be sanitised if $server is using a
365             # non-root path due to potential overlap between request path and
366             # response path.
367             if ($server->path) {
368             # If request path is '/', we have to add a trailing slash to the
369             # final request URI
370 0     0   0 my $add_trailing = ($request->uri->path eq '/' || $request->uri->path eq '') ? 1 : 0;
371              
372             my @sp = split '/', $server->path;
373             my @rp = split '/', $request->uri->path;
374             shift @sp; shift @rp; # leading /
375 0 0       0 if (@rp) {
376             foreach my $sp (@sp) {
377             $sp eq $rp[0] ? shift @rp : last
378 0 0 0     0 }
379             }
380 0         0 $request->uri->path(join '/', @rp);
381 0         0  
382 0         0 if ( $add_trailing ) {
  0         0  
383 0 0       0 $request->uri->path( $request->uri->path . '/' );
384 0         0 }
385 0 0       0 }
386             },
387             }, @_);
388 0         0 }
389              
390 0 0       0 for my $name (qw(local_request remote_request)) {
391 0         0 my $fun = sub {
392             carp <<"EOW";
393             Calling Catalyst::Test::${name}() directly is deprecated.
394              
395 0         0 Please import Catalyst::Test into your namespace and use the provided request()
396             function instead.
397             EOW
398             return __PACKAGE__->can("_${name}")->(@_);
399             };
400 1     1   237  
401             no strict 'refs';
402             *$name = $fun;
403             }
404              
405             my $request = shift;
406 1         17 my $extra_env = shift;
407             my $opts = pop(@_) || {};
408             $opts = {} unless ref($opts) eq 'HASH';
409 126     126   965 if ( my $host = exists $opts->{host} ? $opts->{host} : $default_host ) {
  126         274  
  126         20282  
410             $request->header( 'Host' => $host );
411             }
412              
413             if (my $extra = $opts->{extra_env}) {
414 911     911   1571 @{ $extra_env }{keys %{ $extra }} = values %{ $extra };
415 911         1348 }
416 911   100     4085 }
417 911 100       2683  
418 911 100       3284 =head2 action_ok($url [, $test_name ])
    100          
419 27         90  
420             Fetches the given URL and checks that the request was successful. An optional
421             second argument can be given to specify the name of the test.
422 911 100       4367  
423 2         3 =head2 action_redirect($url [, $test_name ])
  2         7  
  2         5  
  2         6  
424              
425             Fetches the given URL and checks that the request was a redirect. An optional
426             second argument can be given to specify the name of the test.
427              
428             =head2 action_notfound($url [, $test_name ])
429              
430             Fetches the given URL and checks that the request was not found. An optional
431             second argument can be given to specify the name of the test.
432              
433             =head2 content_like( $url, $regexp [, $test_name ] )
434              
435             Fetches the given URL and returns whether the content matches the regexp. An
436             optional third argument can be given to specify the name of the test.
437              
438             =head2 contenttype_is($url, $type [, $test_name ])
439              
440             Verify the given URL has a content type of $type and optionally specify a test name.
441              
442             =head1 SEE ALSO
443              
444             L<Catalyst>, L<Test::WWW::Mechanize::Catalyst>,
445             L<Test::WWW::Selenium::Catalyst>, L<Test::More>, L<HTTP::Request::Common>
446              
447             =head1 AUTHORS
448              
449             Catalyst Contributors, see Catalyst.pm
450              
451             =head1 COPYRIGHT
452              
453             This library is free software. You can redistribute it and/or modify it under
454             the same terms as Perl itself.
455              
456             =begin Pod::Coverage
457              
458             local_request
459              
460             remote_request
461              
462             =end Pod::Coverage
463              
464             =cut
465              
466             1;