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