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