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