document $c->detach with no args
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Manual / Tutorial / Testing.pod
CommitLineData
4d583dd8 1=head1 NAME
2
64ccd8a8 3Catalyst::Manual::Tutorial::Testing - Catalyst Tutorial - Part 7: Testing
4d583dd8 4
4d583dd8 5=head1 OVERVIEW
6
7This is B<Part 7 of 9> for the Catalyst tutorial.
8
64ccd8a8 9L<Tutorial Overview|Catalyst::Manual::Tutorial>
4d583dd8 10
11=over 4
12
13=item 1
14
15L<Introduction|Catalyst::Manual::Tutorial::Intro>
16
17=item 2
18
19L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
20
21=item 3
22
64ccd8a8 23L<Basic CRUD|Catalyst::Manual::Tutorial_BasicCRUD>
4d583dd8 24
25=item 4
26
27L<Authentication|Catalyst::Manual::Tutorial::Authentication>
28
29=item 5
30
31L<Authorization|Catalyst::Manual::Tutorial::Authorization>
32
33=item 6
34
35L<Debugging|Catalyst::Manual::Tutorial::Debugging>
36
37=item 7
38
39B<Testing>
40
41=item 8
42
43L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
44
45=item 9
46
3c098c71 47L<Appendices|Catalyst::Manual::Tutorial::Appendices>
4d583dd8 48
49=back
50
4d583dd8 51=head1 DESCRIPTION
52
64ccd8a8 53You may have noticed that the Catalyst Helper scripts automatically
3c098c71 54create basic C<.t> test scripts under the C<t> directory. This part of
55the tutorial briefly looks at how these tests can be used to not only
56ensure that your application is working correctly at the present time,
57but also provide automated regression testing as you upgrade various
58pieces of your application over time.
4d583dd8 59
936a5dd5 60You can checkout the source code for this example from the catalyst
61subversion repository as per the instructions in
62L<Catalyst::Manual::Tutorial::Intro>
4d583dd8 63
4d583dd8 64=head1 RUNNING THE "CANNED" CATALYST TESTS
65
64ccd8a8 66There are a variety of ways to run Catalyst and Perl tests (for example,
cc548726 67C<perl Makefile.PL> and C<make test>), but one of the easiest is with the
64ccd8a8 68C<prove> command. For example, to run all of the tests in the C<t>
69directory, enter:
4d583dd8 70
71 $ prove --lib lib t
72
64ccd8a8 73The redirection used by the Authentication plugins will cause the
74default C<t/01app.t> to fail. You can fix this by changing the line in
75C<t/01app.t> that read:
4d583dd8 76
77 ok( request('/')->is_success, 'Request should succeed' );
78
79to:
80
81 ok( request('/login')->is_success, 'Request should succeed' );
82
64ccd8a8 83So that a redirect is not necessary. Also, the C<t/controller_Books.t>
84and C<t/controller_Logout.t> default test cases will fail because of the
85authorization. You can delete these two files to prevent false error
86messages:
4d583dd8 87
88 $ rm t/controller_Books.t
89 $ rm t/controller_Logout.t
90
64ccd8a8 91As you can see in the C<prove> command line above, the C<--lib> option
92is used to set the location of the Catalyst C<lib> directory. With this
93command, you will get all of the usual development server debug output,
94something most people prefer to disable while running tests cases.
95Although you can edit the C<lib/MyApp.pm> to comment out the C<-Debug>
96plugin, it's generally easier to simply set the C<CATALYST_DEBUG=0>
97environment variable. For example:
4d583dd8 98
3c098c71 99 $ CATALYST_DEBUG=0 prove --lib lib t
4d583dd8 100
64ccd8a8 101During the C<t/02pod> and C<t/03podcoverage> tests, you might notice the
102C<all skipped: set TEST_POD to enable this test> warning message. To
103execute the Pod-related tests, add C<TEST_POD=1> to the C<prove>
104command:
4d583dd8 105
106 $ CATALYST_DEBUG=0 TEST_POD=1 prove --lib lib t
107
64ccd8a8 108If you omitted the Pod comments from any of the methods that were
109inserted, you might have to go back and fix them to get these tests to
110pass. :-)
4d583dd8 111
64ccd8a8 112Another useful option is the C<verbose> (C<-v>) option to C<prove>. It
113prints the name of each test case as it is being run:
4d583dd8 114
115 $ CATALYST_DEBUG=0 TEST_POD=1 prove --lib lib -v t
116
4d583dd8 117=head1 RUNNING A SINGLE TEST
118
64ccd8a8 119You can also run a single script by appending its name to the C<prove>
120command. For example:
4d583dd8 121
122 $ CATALYST_DEBUG=0 prove --lib lib t/01app.t
123
64ccd8a8 124Note that you can also run tests directly from Perl without C<prove>.
125For example:
4d583dd8 126
127 $ CATALYST_DEBUG=0 perl -Ilib t/01app.t
128
4d583dd8 129=head1 ADDING YOUR OWN TEST SCRIPT
130
64ccd8a8 131Although the Catalyst helper scripts provide a basic level of checks
132"for free," testing can become significantly more helpful when you write
133your own script to exercise the various parts of your application. The
cc548726 134L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst> module
135is very popular for writing these sorts of test cases. This module
136extends L<Test::WWW::Mechanize|Test::WWW::Mechanize> (and therefore
137L<WWW::Mechanize|WWW::Mechanize>) to allow you to automate the action of
64ccd8a8 138a user "clicking around" inside your application. It gives you all the
139benefits of testing on a live system without the messiness of having to
3c098c71 140use an actual web server, and a real person to do the clicking.
4d583dd8 141
64ccd8a8 142To create a sample test case, open the C<t/live_app01.t> file in your
143editor and enter the following:
4d583dd8 144
145 #!/usr/bin/perl
146
147 use strict;
148 use warnings;
149
3c098c71 150 # Load testing framework and use 'no_plan' to dynamically pick up
151 # all tests. Better to replace "'no_plan'" with "tests => 30" so it
152 # knows exactly how many tests need to be run (and will tell you if
153 # not), but 'no_plan' is nice for quick & dirty tests
be16bacd 154
4d583dd8 155 use Test::More 'no_plan';
156
157 # Need to specify the name of your app as arg on next line
158 # Can also do:
159 # use Test::WWW::Mechanize::Catalyst "MyApp";
be16bacd 160
4d583dd8 161 use ok "Test::WWW::Mechanize::Catalyst" => "MyApp";
3c098c71 162
4d583dd8 163 # Create two 'user agents' to simulate two different users ('test01' & 'test02')
164 my $ua1 = Test::WWW::Mechanize::Catalyst->new;
165 my $ua2 = Test::WWW::Mechanize::Catalyst->new;
166
167 # Use a simplified for loop to do tests that are common to both users
168 # Use get_ok() to make sure we can hit the base URL
169 # Second arg = optional description of test (will be displayed for failed tests)
170 # Note that in test scripts you send everything to 'http://localhost'
171 $_->get_ok("http://localhost/", "Check redirect of base URL") for $ua1, $ua2;
172 # Use title_is() to check the contents of the <title>...</title> tags
173 $_->title_is("Login", "Check for login title") for $ua1, $ua2;
9183745a 174 # Use content_contains() to match on text in the html body
4d583dd8 175 $_->content_contains("You need to log in to use this application",
176 "Check we are NOT logged in") for $ua1, $ua2;
177
178 # Log in as each user
179 $ua1->get_ok("http://localhost/login?username=test01&password=mypass", "Login 'test01'");
180 $ua2->get_ok("http://localhost/login?username=test02&password=mypass", "Login 'test02'");
181
182 # Go back to the login page and it should show that we are already logged in
183 $_->get_ok("http://localhost/login", "Return to '/login'") for $ua1, $ua2;
184 $_->title_is("Login", "Check for login page") for $ua1, $ua2;
185 $_->content_contains("Please Note: You are already logged in as ",
186 "Check we ARE logged in" ) for $ua1, $ua2;
187
188 # 'Click' the 'Logout' link
189 $_->follow_link_ok({n => 1}, "Logout via first link on page") for $ua1, $ua2;
190 $_->title_is("Login", "Check for login title") for $ua1, $ua2;
191 $_->content_contains("You need to log in to use this application",
192 "Check we are NOT logged in") for $ua1, $ua2;
193
194 # Log back in
195 $ua1->get_ok("http://localhost/login?username=test01&password=mypass", "Login 'test01'");
196 $ua2->get_ok("http://localhost/login?username=test02&password=mypass", "Login 'test02'");
197 # Should be at the Book List page... do some checks to confirm
198 $_->title_is("Book List", "Check for book list title") for $ua1, $ua2;
199
200 $ua1->get_ok("http://localhost/books/list", "'test01' book list");
201 $ua1->get_ok("http://localhost/login", "Login Page");
202 $ua1->get_ok("http://localhost/books/list", "'test01' book list");
203
204 $_->content_contains("Book List", "Check for book list title") for $ua1, $ua2;
205 # Make sure the appropriate logout buttons are displayed
206 $_->content_contains("/logout\">Logout</a>",
207 "Both users should have a 'User Logout'") for $ua1, $ua2;
208 $ua1->content_contains("/books/form_create\">Create</a>",
209 "Only 'test01' should have a create link");
210
211 $ua1->get_ok("http://localhost/books/list", "View book list as 'test01'");
212
213 # User 'test01' should be able to create a book with the "formless create" URL
214 $ua1->get_ok("http://localhost/books/url_create/TestTitle/2/4",
215 "'test01' formless create");
216 $ua1->title_is("Book Created", "Book created title");
be16bacd 217 $ua1->content_contains("Added book 'TestTitle'", "Check title added OK");
218 $ua1->content_contains("by 'Stevens'", "Check author added OK");
7d310f12 219 $ua1->content_contains("with a rating of 2.", "Check rating added");
220 # Try a regular expression to combine the previous 3 checks & account for whitespace
221 $ua1->content_like(qr/Added book 'TestTitle'\s+by 'Stevens'\s+with a rating of 2./, "Regex check");
4d583dd8 222
223 # Make sure the new book shows in the list
224 $ua1->get_ok("http://localhost/books/list", "'test01' book list");
225 $ua1->title_is("Book List", "Check logged in and at book list");
226 $ua1->content_contains("Book List", "Book List page test");
227 $ua1->content_contains("TestTitle", "Look for 'TestTitle'");
228
229 # Make sure the new book can be deleted
230 # Get all the Delete links on the list page
231 my @delLinks = $ua1->find_all_links(text => 'Delete');
232 # Use the final link to delete the last book
233 $ua1->get_ok($delLinks[$#delLinks]->url, 'Delete last book');
234 # Check that delete worked
235 $ua1->content_contains("Book List", "Book List page test");
236 $ua1->content_contains("Book deleted.", "Book was deleted");
237
238 # User 'test02' should not be able to add a book
239 $ua2->get_ok("http://localhost/books/url_create/TestTitle2/2/5", "'test02' add");
240 $ua2->content_contains("Unauthorized!", "Check 'test02' cannot add");
241
64ccd8a8 242The C<live_app.t> test cases uses copious comments to explain each step
243of the process. In addition to the techniques shown here, there are a
cc548726 244variety of other methods available in
245L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst> (for
246example, regex-based matching). Consult the documentation for more
64ccd8a8 247detail.
4d583dd8 248
64ccd8a8 249B<TIP>: For I<unit tests> vs. the "full application tests" approach used
cc548726 250by L<Test::WWW::Mechanize::Catalyst|Test::WWW::Mechanize::Catalyst>, see
251L<Catalyst::Test|Catalyst::Test>.
4d583dd8 252
64ccd8a8 253B<Note:> The test script does not test the C<form_create> and
254C<form_create_do> actions. That is left as an exercise for the reader
255(you should be able to complete that logic using the existing code as a
256template).
4d583dd8 257
258To run the new test script, use a command such as:
259
260 $ CATALYST_DEBUG=0 prove --lib lib -v t/live_app01.t
261
262or
263
bc384c9d 264 $ DBIC_TRACE=0 CATALYST_DEBUG=0 prove --lib lib -v t/live_app01.t
4d583dd8 265
bc384c9d 266Experiment with the C<DBIC_TRACE>, C<CATALYST_DEBUG>
64ccd8a8 267and C<-v> settings. If you find that there are errors, use the
268techniques discussed in the "Catalyst Debugging" section (Part 6) to
3c098c71 269isolate and fix any problems.
4d583dd8 270
64ccd8a8 271If you want to run the test case under the Perl interactive debugger,
272try a command such as:
4d583dd8 273
bc384c9d 274 $ DBIC_TRACE=0 CATALYST_DEBUG=0 perl -d -Ilib t/live_app01.t
4d583dd8 275
3c098c71 276Note that although this tutorial uses a single custom test case for
64ccd8a8 277simplicity, you may wish to break your tests into different files for
278better organization.
4d583dd8 279
4d583dd8 280=head1 SUPPORTING BOTH PRODUCTION AND TEST DATABASES
281
64ccd8a8 282You may wish to leverage the techniques discussed in this tutorial to
283maintain both a "production database" for your live application and a
284"testing database" for your test cases. One advantage to
3c098c71 285L<Test::WWW::Mechanize::Catalyst> is that
64ccd8a8 286it runs your full application; however, this can complicate things when
287you want to support multiple databases. One solution is to allow the
288database specification to be overridden with an environment variable.
289For example, open C<lib/MyApp/Model/MyAppDB.pm> in your editor and
290change the C<__PACKAGE__-E<gt>config(...> declaration to resemble:
4d583dd8 291
292 my $dsn = $ENV{MYAPP_DSN} ||= 'dbi:SQLite:myapp.db';
293 __PACKAGE__->config(
294 schema_class => 'MyAppDB',
295 connect_info => [
296 $dsn,
297 '',
298 '',
299 { AutoCommit => 1 },
300
301 ],
302 );
303
304Then, when you run your test case, you can use commands such as:
305
306 $ cp myapp.db myappTEST.db
307 $ CATALYST_DEBUG=0 MYAPP_DSN="dbi:SQLite:myappTEST.db" prove --lib lib -v t/live_app01.t
308
64ccd8a8 309This will modify the DSN only while the test case is running. If you
310launch your normal application without the C<MYAPP_DSN> environment
311variable defined, it will default to the same C<dbi:SQLite:myapp.db> as
312before.
4d583dd8 313
cc548726 314
4d583dd8 315=head1 AUTHOR
316
317Kennedy Clark, C<hkclark@gmail.com>
318
eed93301 319Please report any errors, issues or suggestions to the author. The
7d310f12 320most recent version of the Catalyst Tutorial can be found at
eed93301 321L<http://dev.catalyst.perl.org/repos/Catalyst/trunk/Catalyst-Runtime/lib/Catalyst/Manual/Tutorial/>.
4d583dd8 322
64ccd8a8 323Copyright 2006, Kennedy Clark, under Creative Commons License
324(L<http://creativecommons.org/licenses/by-nc-sa/2.5/>).
4d583dd8 325