1 package Test::WWW::Mechanize::Catalyst;
6 require Catalyst::Test; # Do not call import
9 use Test::WWW::Mechanize;
11 extends 'Test::WWW::Mechanize', 'Moose::Object';
13 #use namespace::clean -execept => 'meta';
15 our $VERSION = '0.50';
17 my $Test = Test::Builder->new();
21 predicate => 'has_catalyst_app',
24 has allow_external => (
33 clearer => 'clear_host',
34 predicate => 'has_host',
40 my $args = ref $_[0] ? $_[0] : { @_ };
42 # Dont let LWP complain about options for our attributes
43 my %attr_options = map {
45 defined $n && exists $args->{$n}
46 ? ( $n => delete $args->{$n} )
48 } $class->meta->get_all_attributes;
50 my $obj = $class->SUPER::new(%$args);
51 my $self = $class->meta->new_object(
53 ($APP_CLASS ? (catalyst_app => $APP_CLASS) : () ),
66 unless ($ENV{CATALYST_SERVER}) {
67 croak "catalyst_app attribute is required unless CATALYST_SERVER env variable is set"
68 unless $self->has_catalyst_app;
69 Class::MOP::load_class($self->catalyst_app)
70 unless (Class::MOP::is_class_loaded($self->catalyst_app));
75 my ( $self, $request ) = @_;
77 my $response = $self->_do_catalyst_request($request);
78 $response->header( 'Content-Base', $request->uri );
79 $response->request($request);
80 if ( $request->uri->as_string =~ m{^/} ) {
82 URI->new( 'http://localhost:80/' . $request->uri->as_string ) );
84 $self->cookie_jar->extract_cookies($response) if $self->cookie_jar;
86 # fail tests under the Catalyst debug screen
87 if ( !$self->{catalyst_debug}
88 && $response->code == 500
89 && $response->content =~ /on Catalyst \d+\.\d+/ )
92 = ( $response->content =~ /<code class="error">(.*?)<\/code>/s );
93 $error ||= "unknown error";
94 decode_entities($error);
95 $Test->diag("Catalyst error screen: $error");
96 $response->content('');
97 $response->content_type('');
100 # check if that was a redirect
101 if ( $response->header('Location')
102 && $self->redirect_ok( $request, $response ) )
105 # remember the old response
106 my $old_response = $response;
108 # *where* do they want us to redirect to?
109 my $location = $old_response->header('Location');
111 # no-one *should* be returning non-absolute URLs, but if they
112 # are then we'd better cope with it. Let's create a new URI, using
113 # our request as the base.
114 my $uri = URI->new_abs( $location, $request->uri )->as_string;
116 # make a new response, and save the old response in it
117 $response = $self->_make_request( HTTP::Request->new( GET => $uri ) );
118 my $end_of_chain = $response;
119 while ( $end_of_chain->previous ) # keep going till the end
121 $end_of_chain = $end_of_chain->previous;
123 $end_of_chain->previous($old_response); # ...and add us to it
125 $response->{_raw_content} = $response->content;
131 sub _do_catalyst_request {
132 my ($self, $request) = @_;
134 my $uri = $request->uri;
135 $uri->scheme('http') unless defined $uri->scheme;
136 $uri->host('localhost') unless defined $uri->host;
138 $request = $self->prepare_request($request);
139 $self->cookie_jar->add_cookie_header($request) if $self->cookie_jar;
141 # Woe betide anyone who unsets CATALYST_SERVER
142 return Catalyst::Test::remote_request($request)
143 if $ENV{CATALYST_SERVER};
147 # If there's no Host header, set one.
148 unless ($request->header('Host')) {
149 my $host = $self->has_host
153 $request->header('Host', $host);
156 if ( $self->{allow_external} ) {
157 unless ( $request->uri->as_string =~ m{^/}
158 || $request->uri->host eq 'localhost' )
160 return $self->SUPER::_make_request($request);
164 my @creds = $self->get_basic_credentials( "Basic", $uri );
165 $request->authorization_basic( @creds ) if @creds;
167 return Catalyst::Test::local_request($self->{catalyst_app}, $request);
171 my ($class, $app) = @_;
174 Class::MOP::load_class($app)
175 unless (Class::MOP::is_class_loaded($app));
188 Test::WWW::Mechanize::Catalyst - Test::WWW::Mechanize for Catalyst
192 # We're in a t/*.t test script...
193 use Test::WWW::Mechanize::Catalyst;
195 # To test a Catalyst application named 'Catty':
196 my $mech = Test::WWW::Mechanize::Catalyst->new(catalyst_app => 'Catty');
198 $mech->get_ok("/"); # no hostname needed
199 is($mech->ct, "text/html");
200 $mech->title_is("Root", "On the root page");
201 $mech->content_contains("This is the root page", "Correct content");
202 $mech->follow_link_ok({text => 'Hello'}, "Click on Hello");
203 # ... and all other Test::WWW::Mechanize methods
205 # White label site testing
206 $mech->host("foo.com");
211 L<Catalyst> is an elegant MVC Web Application Framework.
212 L<Test::WWW::Mechanize> is a subclass of L<WWW::Mechanize> that incorporates
213 features for web application testing. The L<Test::WWW::Mechanize::Catalyst>
214 module meshes the two to allow easy testing of L<Catalyst> applications without
215 needing to starting up a web server.
217 Testing web applications has always been a bit tricky, normally
218 requiring starting a web server for your application and making real HTTP
219 requests to it. This module allows you to test L<Catalyst> web
220 applications but does not require a server or issue HTTP
221 requests. Instead, it passes the HTTP request object directly to
222 L<Catalyst>. Thus you do not need to use a real hostname:
223 "http://localhost/" will do. However, this is optional. The following
224 two lines of code do exactly the same thing:
226 $mech->get_ok('/action');
227 $mech->get_ok('http://localhost/action');
229 Links which do not begin with / or are not for localhost can be handled
230 as normal Web requests - this is handy if you have an external
231 single sign-on system. You must set allow_external to true for this:
233 $mech->allow_external(1);
235 You can also test a remote server by setting the environment variable
236 CATALYST_SERVER; for example:
238 $ CATALYST_SERVER=http://example.com/myapp prove -l t
240 will run the same tests on the application running at
241 http://example.com/myapp regardless of whether or not you specify
242 http:://localhost for Test::WWW::Mechanize::Catalyst.
244 Furthermore, if you set CATALYST_SERVER, the server will be regarded
245 as a remote server even if your links point to localhost. Thus, you
246 can use Test::WWW::Mechanize::Catalyst to test your live webserver
247 running on your local machine, if you need to test aspects of your
248 deployment environment (for example, configuration options in an
249 http.conf file) instead of just the Catalyst request handling.
251 This makes testing fast and easy. L<Test::WWW::Mechanize> provides
252 functions for common web testing scenarios. For example:
254 $mech->get_ok( $page );
255 $mech->title_is( "Invoice Status", "Make sure we're on the invoice page" );
256 $mech->content_contains( "Andy Lester", "My name somewhere" );
257 $mech->content_like( qr/(cpan|perl)\.org/, "Link to perl.org or CPAN" );
259 This module supports cookies automatically.
261 To use this module you must pass it the name of the application. See
264 Note that Catalyst has a special developing feature: the debug
265 screen. By default this module will treat responses which are the
266 debug screen as failures. If you actually want to test debug screens,
269 $mmech->{catalyst_debug} = 1;
271 An alternative to this module is L<Catalyst::Test>.
277 Behaves like, and calls, L<WWW::Mechanize>'s C<new> method. Any params
278 passed in get passed to WWW::Mechanize's constructor. Note that we
279 need to pass the name of the Catalyst application to the "use":
281 use Test::WWW::Mechanize::Catalyst 'Catty';
282 my $mech = Test::WWW::Mechanize::Catalyst->new;
286 =head2 allow_external
288 Links which do not begin with / or are not for localhost can be handled
289 as normal Web requests - this is handy if you have an external
290 single sign-on system. You must set allow_external to true for this:
292 $m->allow_external(1);
296 The name of the Catalyst app which we are testing against. Read-only.
300 The host value to set the "Host:" HTTP header to, if none is present already in
301 the request. If not set (default) then Catalyst::Test will set this to
306 Unset the host attribute.
310 Do we have a value set for the host attribute
312 =head2 $mech->get_ok($url, [ \%LWP_options ,] $desc)
314 A wrapper around WWW::Mechanize's get(), with similar options, except the
315 second argument needs to be a hash reference, not a hash. Returns true or
318 =head2 $mech->title_is( $str [, $desc ] )
320 Tells if the title of the page is the given string.
322 $mech->title_is( "Invoice Summary" );
324 =head2 $mech->title_like( $regex [, $desc ] )
326 Tells if the title of the page matches the given regex.
328 $mech->title_like( qr/Invoices for (.+)/
330 =head2 $mech->title_unlike( $regex [, $desc ] )
332 Tells if the title of the page does NOT match the given regex.
334 $mech->title_unlike( qr/Invoices for (.+)/
336 =head2 $mech->content_is( $str [, $desc ] )
338 Tells if the content of the page matches the given string
340 =head2 $mech->content_contains( $str [, $desc ] )
342 Tells if the content of the page contains I<$str>.
344 =head2 $mech->content_lacks( $str [, $desc ] )
346 Tells if the content of the page lacks I<$str>.
348 =head2 $mech->content_like( $regex [, $desc ] )
350 Tells if the content of the page matches I<$regex>.
352 =head2 $mech->content_unlike( $regex [, $desc ] )
354 Tells if the content of the page does NOT match I<$regex>.
356 =head2 $mech->page_links_ok( [ $desc ] )
358 Follow all links on the current page and test for HTTP status 200
360 $mech->page_links_ok('Check all links');
362 =head2 $mech->page_links_content_like( $regex,[ $desc ] )
364 Follow all links on the current page and test their contents for I<$regex>.
366 $mech->page_links_content_like( qr/foo/,
367 'Check all links contain "foo"' );
369 =head2 $mech->page_links_content_unlike( $regex,[ $desc ] )
371 Follow all links on the current page and test their contents do not
372 contain the specified regex.
374 $mech->page_links_content_unlike(qr/Restricted/,
375 'Check all links do not contain Restricted');
377 =head2 $mech->links_ok( $links [, $desc ] )
379 Check the current page for specified links and test for HTTP status
380 200. The links may be specified as a reference to an array containing
381 L<WWW::Mechanize::Link> objects, an array of URLs, or a scalar URL
384 my @links = $mech->find_all_links( url_regex => qr/cnn\.com$/ );
385 $mech->links_ok( \@links, 'Check all links for cnn.com' );
387 my @links = qw( index.html search.html about.html );
388 $mech->links_ok( \@links, 'Check main links' );
390 $mech->links_ok( 'index.html', 'Check link to index' );
392 =head2 $mech->link_status_is( $links, $status [, $desc ] )
394 Check the current page for specified links and test for HTTP status
395 passed. The links may be specified as a reference to an array
396 containing L<WWW::Mechanize::Link> objects, an array of URLs, or a
399 my @links = $mech->links();
400 $mech->link_status_is( \@links, 403,
401 'Check all links are restricted' );
403 =head2 $mech->link_status_isnt( $links, $status [, $desc ] )
405 Check the current page for specified links and test for HTTP status
406 passed. The links may be specified as a reference to an array
407 containing L<WWW::Mechanize::Link> objects, an array of URLs, or a
410 my @links = $mech->links();
411 $mech->link_status_isnt( \@links, 404,
412 'Check all links are not 404' );
414 =head2 $mech->link_content_like( $links, $regex [, $desc ] )
416 Check the current page for specified links and test the content of
417 each against I<$regex>. The links may be specified as a reference to
418 an array containing L<WWW::Mechanize::Link> objects, an array of URLs,
419 or a scalar URL name.
421 my @links = $mech->links();
422 $mech->link_content_like( \@links, qr/Restricted/,
423 'Check all links are restricted' );
425 =head2 $mech->link_content_unlike( $links, $regex [, $desc ] )
427 Check the current page for specified links and test the content of each
428 does not match I<$regex>. The links may be specified as a reference to
429 an array containing L<WWW::Mechanize::Link> objects, an array of URLs,
430 or a scalar URL name.
432 my @links = $mech->links();
433 $mech->link_content_like( \@links, qr/Restricted/,
434 'Check all links are restricted' );
436 =head2 follow_link_ok( \%parms [, $comment] )
438 Makes a C<follow_link()> call and executes tests on the results.
439 The link must be found, and then followed successfully. Otherwise,
442 I<%parms> is a hashref containing the params to pass to C<follow_link()>.
443 Note that the params to C<follow_link()> are a hash whereas the parms to
444 this function are a hashref. You have to call this function like:
446 $agent->follow_like_ok( {n=>3}, "looking for 3rd link" );
448 As with other test functions, C<$comment> is optional. If it is supplied
449 then it will display when running the test harness in verbose mode.
451 Returns true value if the specified link was found and followed
452 successfully. The HTTP::Response object returned by follow_link()
457 Related modules which may be of interest: L<Catalyst>,
458 L<Test::WWW::Mechanize>, L<WWW::Mechanize>.
462 Ash Berlin C<< <ash@cpan.org> >> (current maintiner)
464 Original Author: Leon Brocard, C<< <acme@astray.com> >>
468 Copyright (C) 2005-8, Leon Brocard
472 This module is free software; you can redistribute it or modify it
473 under the same terms as Perl itself.