initial import of new Tutorial stuff from hkclark
[catagits/Catalyst-Runtime.git] / lib / Catalyst / Manual / Tutorial / Testing.pod
1 =head1 NAME
2
3 Catalyst::Manual::Tutorial::Testing - Catalyst Tutorial – Part 7: Testing
4
5
6
7 =head1 OVERVIEW
8
9 This is B<Part 7 of 9> for the Catalyst tutorial.
10
11 L<Totorial Overview|Catalyst::Manual::Tutorial>
12
13 =over 4
14
15 =item 1
16
17 L<Introduction|Catalyst::Manual::Tutorial::Intro>
18
19 =item 2
20
21 L<Catalyst Basics|Catalyst::Manual::Tutorial::CatalystBasics>
22
23 =item 3
24
25 L<Basic CRUD|Catalyst::Manual::Tutorial03_BasicCRUD>
26
27 =item 4
28
29 L<Authentication|Catalyst::Manual::Tutorial::Authentication>
30
31 =item 5
32
33 L<Authorization|Catalyst::Manual::Tutorial::Authorization>
34
35 =item 6
36
37 L<Debugging|Catalyst::Manual::Tutorial::Debugging>
38
39 =item 7
40
41 B<Testing>
42
43 =item 8
44
45 L<AdvancedCRUD|Catalyst::Manual::Tutorial::AdvancedCRUD>
46
47 =item 9
48
49 L<Appendicies|Catalyst::Manual::Tutorial::Appendicies>
50
51 =back
52
53
54
55 =head1 DESCRIPTION
56
57
58 You 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
60 B<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
69 There 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
73 The 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
77 to:
78
79     ok( request('/login')->is_success, 'Request should succeed' );
80
81 So 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
86 As 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
90 During 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
94 If 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
96 Another 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
104 You 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
108 Note 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
115 Although 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
117 To 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
211 The 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
213 B<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
215 B<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
217 To run the new test script, use a command such as:
218
219     $ CATALYST_DEBUG=0 prove --lib lib -v t/live_app01.t
220
221 or
222
223     $ DBIX_CLASS_STORAGE_DBI_DEBUG=0 CATALYST_DEBUG=0 prove --lib lib -v t/live_app01.t
224
225 Experiment 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
227 If 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
231 Note 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
237 You 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
251 Then, 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
256 This 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
262 Kennedy Clark, C<hkclark@gmail.com>
263
264 Please report any errors, issues or suggestions to the author.
265
266 Copyright 2006, Kennedy Clark. All rights reserved.
267
268 This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
269
270 Version: .94
271