Commit | Line | Data |
d442cc9f |
1 | =head1 NAME |
2 | |
3ab6187c |
3 | Catalyst::Manual::Tutorial::08_Testing - Catalyst Tutorial - Chapter 8: Testing |
d442cc9f |
4 | |
5 | |
6 | =head1 OVERVIEW |
7 | |
4b4d3884 |
8 | This is B<Chapter 8 of 10> for the Catalyst tutorial. |
d442cc9f |
9 | |
10 | L<Tutorial Overview|Catalyst::Manual::Tutorial> |
11 | |
12 | =over 4 |
13 | |
14 | =item 1 |
15 | |
3ab6187c |
16 | L<Introduction|Catalyst::Manual::Tutorial::01_Intro> |
d442cc9f |
17 | |
18 | =item 2 |
19 | |
3ab6187c |
20 | L<Catalyst Basics|Catalyst::Manual::Tutorial::02_CatalystBasics> |
d442cc9f |
21 | |
22 | =item 3 |
23 | |
3ab6187c |
24 | L<More Catalyst Basics|Catalyst::Manual::Tutorial::03_MoreCatalystBasics> |
d442cc9f |
25 | |
26 | =item 4 |
27 | |
3ab6187c |
28 | L<Basic CRUD|Catalyst::Manual::Tutorial::04_BasicCRUD> |
d442cc9f |
29 | |
30 | =item 5 |
31 | |
3ab6187c |
32 | L<Authentication|Catalyst::Manual::Tutorial::05_Authentication> |
d442cc9f |
33 | |
34 | =item 6 |
35 | |
3ab6187c |
36 | L<Authorization|Catalyst::Manual::Tutorial::06_Authorization> |
d442cc9f |
37 | |
38 | =item 7 |
39 | |
3ab6187c |
40 | L<Debugging|Catalyst::Manual::Tutorial::07_Debugging> |
d442cc9f |
41 | |
42 | =item 8 |
43 | |
3ab6187c |
44 | B<08_Testing> |
d442cc9f |
45 | |
46 | =item 9 |
47 | |
3ab6187c |
48 | L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD> |
3533daff |
49 | |
50 | =item 10 |
51 | |
3ab6187c |
52 | L<Appendices|Catalyst::Manual::Tutorial::10_Appendices> |
d442cc9f |
53 | |
54 | =back |
55 | |
3533daff |
56 | |
d442cc9f |
57 | =head1 DESCRIPTION |
58 | |
bf4d990b |
59 | You may have noticed that the Catalyst Helper scripts automatically |
60 | create basic C<.t> test scripts under the C<t> directory. This chapter |
61 | of the tutorial briefly looks at how these tests can be used not only to |
62 | ensure that your application is working correctly at the present time, |
63 | but also provide automated regression testing as you upgrade various |
64 | pieces of your application over time. |
d442cc9f |
65 | |
b1b6582a |
66 | Source code for the tutorial in included in the F</root/Final> directory |
67 | of the Tutorial Virtual machine (one subdirectory per chapter). There |
68 | are also instructions for downloading the code in |
2217b252 |
69 | L<Catalyst::Manual::Tutorial::01_Intro>. |
1390ef0e |
70 | |
3b1fa91b |
71 | For an excellent introduction to learning the many benefits of testing |
bf4d990b |
72 | your Perl applications and modules, you might want to read 'Perl |
73 | Testing: A Developer's Notebook' by Ian Langworth and chromatic. |
3b1fa91b |
74 | |
d442cc9f |
75 | |
76 | =head1 RUNNING THE "CANNED" CATALYST TESTS |
77 | |
78 | There are a variety of ways to run Catalyst and Perl tests (for example, |
bf4d990b |
79 | C<perl Makefile.PL> and C<make test>), but one of the easiest is with |
80 | the C<prove> command. For example, to run all of the tests in the C<t> |
d442cc9f |
81 | directory, enter: |
82 | |
da59dbea |
83 | $ prove -wl t |
d442cc9f |
84 | |
bf4d990b |
85 | There will be a lot of output because we have the C<-Debug> flag enabled |
86 | in C<lib/MyApp.pm> (see the C<CATALYST_DEBUG=0> tip below for a quick |
87 | and easy way to reduce the clutter). Look for lines like this for |
88 | errors: |
3533daff |
89 | |
90 | # Failed test 'Request should succeed' |
3b1fa91b |
91 | # at t/controller_Books.t line 8. |
3533daff |
92 | # Looks like you failed 1 test of 3. |
93 | |
bf4d990b |
94 | The redirection used by the Authentication plugins will cause several |
3533daff |
95 | failures in the default tests. You can fix this by making the following |
96 | changes: |
97 | |
acbd7bdd |
98 | 1) Change the line in C<t/01app.t> that reads: |
d442cc9f |
99 | |
100 | ok( request('/')->is_success, 'Request should succeed' ); |
101 | |
102 | to: |
103 | |
104 | ok( request('/login')->is_success, 'Request should succeed' ); |
105 | |
3b1fa91b |
106 | 2) Change the line in C<t/controller_Logout.t> that reads: |
107 | |
108 | ok( request('/logout')->is_success, 'Request should succeed' ); |
109 | |
110 | to: |
111 | |
112 | ok( request('/logout')->is_redirect, 'Request should succeed' ); |
113 | |
114 | 3) Change the line in C<t/controller_Books.t> that reads: |
115 | |
116 | ok( request('/books')->is_success, 'Request should succeed' ); |
117 | |
118 | to: |
119 | |
120 | ok( request('/books')->is_redirect, 'Request should succeed' ); |
d442cc9f |
121 | |
335a4fdf |
122 | 4) Add the following statement to the top of C<t/view_HTML.t>: |
d442cc9f |
123 | |
3b1fa91b |
124 | use MyApp; |
6a72d1bf |
125 | |
0e662618 |
126 | As you can see in the C<prove> command line above, the C<-l> option (or |
127 | C<--lib> if you prefer) is used to set the location of the Catalyst |
128 | C<lib> directory. With this command, you will get all of the usual |
129 | development server debug output, something most people prefer to disable |
130 | while running tests cases. Although you can edit the C<lib/MyApp.pm> to |
131 | comment out the C<-Debug> plugin, it's generally easier to simply set |
132 | the C<CATALYST_DEBUG=0> environment variable. For example: |
d442cc9f |
133 | |
da59dbea |
134 | $ CATALYST_DEBUG=0 prove -wl t |
d442cc9f |
135 | |
136 | During the C<t/02pod> and C<t/03podcoverage> tests, you might notice the |
137 | C<all skipped: set TEST_POD to enable this test> warning message. To |
138 | execute the Pod-related tests, add C<TEST_POD=1> to the C<prove> |
139 | command: |
140 | |
da59dbea |
141 | $ CATALYST_DEBUG=0 TEST_POD=1 prove -wl t |
d442cc9f |
142 | |
143 | If you omitted the Pod comments from any of the methods that were |
144 | inserted, you might have to go back and fix them to get these tests to |
145 | pass. :-) |
146 | |
147 | Another useful option is the C<verbose> (C<-v>) option to C<prove>. It |
148 | prints the name of each test case as it is being run: |
149 | |
0e662618 |
150 | $ CATALYST_DEBUG=0 prove -vwl t |
d442cc9f |
151 | |
3533daff |
152 | |
d442cc9f |
153 | =head1 RUNNING A SINGLE TEST |
154 | |
155 | You can also run a single script by appending its name to the C<prove> |
156 | command. For example: |
157 | |
da59dbea |
158 | $ CATALYST_DEBUG=0 prove -wl t/01app.t |
d442cc9f |
159 | |
bf4d990b |
160 | Also note that you can also run tests directly from Perl without |
161 | C<prove>. For example: |
d442cc9f |
162 | |
da59dbea |
163 | $ CATALYST_DEBUG=0 perl -w -Ilib t/01app.t |
d442cc9f |
164 | |
3533daff |
165 | |
d442cc9f |
166 | =head1 ADDING YOUR OWN TEST SCRIPT |
167 | |
168 | Although the Catalyst helper scripts provide a basic level of checks |
169 | "for free," testing can become significantly more helpful when you write |
0e662618 |
170 | your own tests to exercise the various parts of your application. The |
bf4d990b |
171 | L<Test::WWW::Mechanize::Catalyst> module is very popular for writing |
172 | these sorts of test cases. This module extends L<Test::WWW::Mechanize> |
173 | (and therefore L<WWW::Mechanize>) to allow you to automate the action of |
d442cc9f |
174 | a user "clicking around" inside your application. It gives you all the |
175 | benefits of testing on a live system without the messiness of having to |
176 | use an actual web server, and a real person to do the clicking. |
177 | |
178 | To create a sample test case, open the C<t/live_app01.t> file in your |
179 | editor and enter the following: |
180 | |
0e662618 |
181 | #!/usr/bin/env perl |
d442cc9f |
182 | |
183 | use strict; |
184 | use warnings; |
da59dbea |
185 | use Test::More; |
d442cc9f |
186 | |
187 | # Need to specify the name of your app as arg on next line |
188 | # Can also do: |
189 | # use Test::WWW::Mechanize::Catalyst "MyApp"; |
190 | |
e5415384 |
191 | BEGIN { use_ok("Test::WWW::Mechanize::Catalyst" => "MyApp") } |
3dba69ab |
192 | |
d442cc9f |
193 | # Create two 'user agents' to simulate two different users ('test01' & 'test02') |
194 | my $ua1 = Test::WWW::Mechanize::Catalyst->new; |
195 | my $ua2 = Test::WWW::Mechanize::Catalyst->new; |
196 | |
197 | # Use a simplified for loop to do tests that are common to both users |
198 | # Use get_ok() to make sure we can hit the base URL |
199 | # Second arg = optional description of test (will be displayed for failed tests) |
200 | # Note that in test scripts you send everything to 'http://localhost' |
201 | $_->get_ok("http://localhost/", "Check redirect of base URL") for $ua1, $ua2; |
202 | # Use title_is() to check the contents of the <title>...</title> tags |
203 | $_->title_is("Login", "Check for login title") for $ua1, $ua2; |
204 | # Use content_contains() to match on text in the html body |
205 | $_->content_contains("You need to log in to use this application", |
206 | "Check we are NOT logged in") for $ua1, $ua2; |
207 | |
208 | # Log in as each user |
209 | # Specify username and password on the URL |
210 | $ua1->get_ok("http://localhost/login?username=test01&password=mypass", "Login 'test01'"); |
2a6eb5f9 |
211 | # Could make user2 like user1 above, but use the form to show another way |
212 | $ua2->submit_form( |
213 | fields => { |
214 | username => 'test02', |
215 | password => 'mypass', |
216 | }); |
d442cc9f |
217 | |
218 | # Go back to the login page and it should show that we are already logged in |
219 | $_->get_ok("http://localhost/login", "Return to '/login'") for $ua1, $ua2; |
220 | $_->title_is("Login", "Check for login page") for $ua1, $ua2; |
221 | $_->content_contains("Please Note: You are already logged in as ", |
222 | "Check we ARE logged in" ) for $ua1, $ua2; |
223 | |
224 | # 'Click' the 'Logout' link (see also 'text_regex' and 'url_regex' options) |
028b4e1a |
225 | $_->follow_link_ok({n => 4}, "Logout via first link on page") for $ua1, $ua2; |
d442cc9f |
226 | $_->title_is("Login", "Check for login title") for $ua1, $ua2; |
227 | $_->content_contains("You need to log in to use this application", |
228 | "Check we are NOT logged in") for $ua1, $ua2; |
229 | |
230 | # Log back in |
0e662618 |
231 | $ua1->get_ok("http://localhost/login?username=test01&password=mypass", |
232 | "Login 'test01'"); |
233 | $ua2->get_ok("http://localhost/login?username=test02&password=mypass", |
234 | "Login 'test02'"); |
d442cc9f |
235 | # Should be at the Book List page... do some checks to confirm |
236 | $_->title_is("Book List", "Check for book list title") for $ua1, $ua2; |
237 | |
238 | $ua1->get_ok("http://localhost/books/list", "'test01' book list"); |
239 | $ua1->get_ok("http://localhost/login", "Login Page"); |
240 | $ua1->get_ok("http://localhost/books/list", "'test01' book list"); |
241 | |
242 | $_->content_contains("Book List", "Check for book list title") for $ua1, $ua2; |
243 | # Make sure the appropriate logout buttons are displayed |
fbbb9084 |
244 | $_->content_contains("/logout\">User Logout</a>", |
d442cc9f |
245 | "Both users should have a 'User Logout'") for $ua1, $ua2; |
6290bf87 |
246 | $ua1->content_contains("/books/form_create\">Admin Create</a>", |
87058ad4 |
247 | "'test01' should have a create link"); |
248 | $ua2->content_lacks("/books/form_create\">Admin Create</a>", |
249 | "'test02' should NOT have a create link"); |
d442cc9f |
250 | |
251 | $ua1->get_ok("http://localhost/books/list", "View book list as 'test01'"); |
252 | |
253 | # User 'test01' should be able to create a book with the "formless create" URL |
254 | $ua1->get_ok("http://localhost/books/url_create/TestTitle/2/4", |
255 | "'test01' formless create"); |
256 | $ua1->title_is("Book Created", "Book created title"); |
257 | $ua1->content_contains("Added book 'TestTitle'", "Check title added OK"); |
258 | $ua1->content_contains("by 'Stevens'", "Check author added OK"); |
259 | $ua1->content_contains("with a rating of 2.", "Check rating added"); |
260 | # Try a regular expression to combine the previous 3 checks & account for whitespace |
0e662618 |
261 | $ua1->content_like(qr/Added book 'TestTitle'\s+by 'Stevens'\s+with a rating of 2./, |
262 | "Regex check"); |
d442cc9f |
263 | |
264 | # Make sure the new book shows in the list |
265 | $ua1->get_ok("http://localhost/books/list", "'test01' book list"); |
266 | $ua1->title_is("Book List", "Check logged in and at book list"); |
267 | $ua1->content_contains("Book List", "Book List page test"); |
268 | $ua1->content_contains("TestTitle", "Look for 'TestTitle'"); |
269 | |
270 | # Make sure the new book can be deleted |
271 | # Get all the Delete links on the list page |
272 | my @delLinks = $ua1->find_all_links(text => 'Delete'); |
273 | # Use the final link to delete the last book |
274 | $ua1->get_ok($delLinks[$#delLinks]->url, 'Delete last book'); |
275 | # Check that delete worked |
276 | $ua1->content_contains("Book List", "Book List page test"); |
0e662618 |
277 | $ua1->content_like(qr/Deleted book \d+/, "Deleted book #"); |
d442cc9f |
278 | |
279 | # User 'test02' should not be able to add a book |
280 | $ua2->get_ok("http://localhost/books/url_create/TestTitle2/2/5", "'test02' add"); |
281 | $ua2->content_contains("Unauthorized!", "Check 'test02' cannot add"); |
0a2a4a5a |
282 | |
da59dbea |
283 | done_testing; |
284 | |
d442cc9f |
285 | The C<live_app.t> test cases uses copious comments to explain each step |
286 | of the process. In addition to the techniques shown here, there are a |
bf4d990b |
287 | variety of other methods available in L<Test::WWW::Mechanize::Catalyst> |
0e662618 |
288 | (for example, regex-based matching). Consult |
289 | L<Test::WWW::Mechanize::Catalyst>, L<Test::WWW::Mechanize>, |
290 | L<WWW::Mechanize>, and L<Test::More> for more detail. |
d442cc9f |
291 | |
292 | B<TIP>: For I<unit tests> vs. the "full application tests" approach used |
bf4d990b |
293 | by L<Test::WWW::Mechanize::Catalyst>, see L<Catalyst::Test>. |
d442cc9f |
294 | |
295 | B<Note:> The test script does not test the C<form_create> and |
296 | C<form_create_do> actions. That is left as an exercise for the reader |
297 | (you should be able to complete that logic using the existing code as a |
298 | template). |
299 | |
300 | To run the new test script, use a command such as: |
301 | |
da59dbea |
302 | $ CATALYST_DEBUG=0 prove -vwl t/live_app01.t |
d442cc9f |
303 | |
304 | or |
305 | |
da59dbea |
306 | $ DBIC_TRACE=0 CATALYST_DEBUG=0 prove -vwl t/live_app01.t |
d442cc9f |
307 | |
bf4d990b |
308 | Experiment with the C<DBIC_TRACE>, C<CATALYST_DEBUG> and C<-v> settings. |
309 | If you find that there are errors, use the techniques discussed in the |
310 | "Catalyst Debugging" section (Chapter 7) to isolate and fix any |
311 | problems. |
d442cc9f |
312 | |
313 | If you want to run the test case under the Perl interactive debugger, |
314 | try a command such as: |
315 | |
316 | $ DBIC_TRACE=0 CATALYST_DEBUG=0 perl -d -Ilib t/live_app01.t |
317 | |
318 | Note that although this tutorial uses a single custom test case for |
319 | simplicity, you may wish to break your tests into different files for |
320 | better organization. |
321 | |
322 | B<TIP:> If you have a test case that fails, you will receive an error |
323 | similar to the following: |
324 | |
325 | # Failed test 'Check we are NOT logged in' |
326 | # in t/live_app01.t at line 31. |
327 | # searched: "\x{0a}<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Tran"... |
328 | # can't find: "You need to log in to use this application." |
329 | |
330 | Unfortunately, this only shows us the first 50 characters of the HTML |
331 | returned by the request -- not enough to determine where the problem |
bf4d990b |
332 | lies. A simple technique that can be used in such situations is to |
333 | temporarily insert a line similar to the following right after the |
d442cc9f |
334 | failed test: |
335 | |
6daaedc0 |
336 | diag $ua1->content; |
d442cc9f |
337 | |
338 | This will cause the full HTML returned by the request to be displayed. |
339 | |
bf4d990b |
340 | Another approach to see the full HTML content at the failure point in a |
341 | series of tests would be to insert a "C<$DB::single=1;> right above the |
0e662618 |
342 | location of the failure and run the test under the Perl debugger (with |
bf4d990b |
343 | C<-d>) as shown above. Then you can use the debugger to explore the |
344 | state of the application right before or after the failure. |
fbbb9084 |
345 | |
d442cc9f |
346 | |
347 | =head1 SUPPORTING BOTH PRODUCTION AND TEST DATABASES |
348 | |
349 | You may wish to leverage the techniques discussed in this tutorial to |
350 | maintain both a "production database" for your live application and a |
351 | "testing database" for your test cases. One advantage to |
bf4d990b |
352 | L<Test::WWW::Mechanize::Catalyst> is that it runs your full application; |
353 | however, this can complicate things when you want to support multiple |
354 | databases. |
6c0a745e |
355 | |
356 | =head2 DATABASE CONFIG SWITCHING IN YOUR MODEL CLASS |
357 | |
bf4d990b |
358 | One solution is to allow the database specification to be overridden |
359 | with an environment variable. For example, open |
360 | C<lib/MyApp/Model/DB.pm> in your editor and change the |
361 | C<__PACKAGE__-E<gt>config(...> declaration to resemble: |
d442cc9f |
362 | |
363 | my $dsn = $ENV{MYAPP_DSN} ||= 'dbi:SQLite:myapp.db'; |
364 | __PACKAGE__->config( |
d0496197 |
365 | schema_class => 'MyApp::Schema', |
0a2a4a5a |
366 | |
da59dbea |
367 | connect_info => { |
368 | dsn => $dsn, |
0a2a4a5a |
369 | user => '', |
370 | password => '', |
371 | on_connect_do => q{PRAGMA foreign_keys = ON}, |
372 | } |
373 | ); |
d442cc9f |
374 | |
375 | Then, when you run your test case, you can use commands such as: |
376 | |
377 | $ cp myapp.db myappTEST.db |
da59dbea |
378 | $ CATALYST_DEBUG=0 MYAPP_DSN="dbi:SQLite:myappTEST.db" prove -vwl t/live_app01.t |
d442cc9f |
379 | |
380 | This will modify the DSN only while the test case is running. If you |
381 | launch your normal application without the C<MYAPP_DSN> environment |
382 | variable defined, it will default to the same C<dbi:SQLite:myapp.db> as |
383 | before. |
384 | |
bf4d990b |
385 | |
6c0a745e |
386 | =head2 DATABASE CONFIG SWITCHING USING MULTIPLE CONFIG FILES |
387 | |
0e662618 |
388 | L<Catalyst::Plugin::ConfigLoader> has functionality to load loading |
389 | multiple config files based on environment variablesi, allowing you to |
390 | override your default (production) database connection settings during |
391 | development (or vice versa). |
6c0a745e |
392 | |
bf4d990b |
393 | Setting C<$ENV{ MYAPP_CONFIG_LOCAL_SUFFIX }> to 'testing' in your test |
394 | script results in loading of an additional config file named |
395 | C<myapp_testing.conf> after C<myapp.conf> which will override any |
396 | parameters in C<myapp.conf>. |
6c0a745e |
397 | |
bf4d990b |
398 | You should set the environment variable in the BEGIN block of your test |
399 | script to make sure it's set before your Catalyst application is |
400 | started. |
6c0a745e |
401 | |
bf4d990b |
402 | The following is an example for a config and test script for a |
403 | DBIx::Class model named MyDB and a controller named Foo: |
6c0a745e |
404 | |
405 | myapp_testing.conf: |
406 | |
407 | <Model::MyDB> |
408 | <connect_info> |
409 | dsn dbi:SQLite:myapp.db |
410 | </connect_info> |
411 | </Model::MyDB> |
412 | |
413 | |
414 | t/controller_Foo.t: |
415 | |
416 | use strict; |
417 | use warnings; |
418 | use Test::More; |
3dba69ab |
419 | |
6c0a745e |
420 | BEGIN { |
421 | $ENV{ MYAPP_CONFIG_LOCAL_SUFFIX } = 'testing'; |
422 | } |
3dba69ab |
423 | |
6c0a745e |
424 | eval "use Test::WWW::Mechanize::Catalyst 'MyApp'"; |
425 | plan $@ |
426 | ? ( skip_all => 'Test::WWW::Mechanize::Catalyst required' ) |
427 | : ( tests => 2 ); |
3dba69ab |
428 | |
6c0a745e |
429 | ok( my $mech = Test::WWW::Mechanize::Catalyst->new, 'Created mech object' ); |
3dba69ab |
430 | |
6c0a745e |
431 | $mech->get_ok( 'http://localhost/foo' ); |
432 | |
d442cc9f |
433 | |
24acc5d7 |
434 | You can jump to the next chapter of the tutorial here: |
435 | L<Advanced CRUD|Catalyst::Manual::Tutorial::09_AdvancedCRUD> |
436 | |
437 | |
d442cc9f |
438 | =head1 AUTHOR |
439 | |
440 | Kennedy Clark, C<hkclark@gmail.com> |
441 | |
53243324 |
442 | Feel free to contact the author for any errors or suggestions, but the |
443 | best way to report issues is via the CPAN RT Bug system at |
444 | <https://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Manual>. |
445 | |
446 | The most recent version of the Catalyst Tutorial can be found at |
59884771 |
447 | L<http://dev.catalyst.perl.org/repos/Catalyst/Catalyst-Manual/5.80/trunk/lib/Catalyst/Manual/Tutorial/>. |
d442cc9f |
448 | |
ec3ef4ad |
449 | Copyright 2006-2010, Kennedy Clark, under the |
450 | Creative Commons Attribution Share-Alike License Version 3.0 |
8482d557 |
451 | (L<http://creativecommons.org/licenses/by-sa/3.0/us/>). |