Fix pod formatting for metacpan sanity
[catagits/Catalyst-Manual-Monthly.git] / lib / Catalyst / Manual / Monthly / 2012 / February / TestDBICWithBellsOn.pod
CommitLineData
ac6a6870 1=head1 NAME
2
3Catalyst::Manual::Monthly::2012::February::TestDBICWithBellsOn
4
5=head2 Testing difficult-to-test database models
61b85627 6
b9e2809b 7In this article we're going to describe a technique for testing database-heavy
8web applications using either a temporary testing database, or using the DSN
9defined in your Catalyst application depending on the presence of an
10environment variable. If the latter, we will not delete the contents of the
11database afterwards, because this workflow suggests that we will want to poke
12around our application manually with the application in a known state.
13
14Basically, providing automated testing of complex databases is a pain. For
61b85627 15generic type functions (e.g. the development of libraries rather than
16applications), mock objects (objects that mimic the interface of a real
b9e2809b 17object) are useful for unit testing. But in the running on the
18seat-of-your-pants development style that commercial web applications often
19require, small changes to functionality can wreak havoc with your mock
61b85627 20objects, and they rapidly become more trouble than they're worth.
21
22Which is where L<Test::DBIx::Class> comes in. The rest of this article
23will explain how to achieve three different use-cases for Test::DBIx::Class
24and Catalyst.
25
26=head2 The easy use case - Inferring the Database from the DBIC result classes.
27
28For a straightforward database schema where the L<DBIx::Class> (DBIC)
29result classes can be used out of the box, one can simply use
30L<Test::DBIx::Class> to infer the database schema into a temporary
31database, using a temporary Postgresql instance via
32L<Test::DBIx::Class::SchemaManager::Trait::Testpostgresql>:
33
34 use Test::More;
35
36 use strict;
37 use warnings;
38 use Test::WWW::Mechanize::Catalyst 'MyApp';
39
40 use Test::DBIx::Class {
41 schema_class => MyApp->model('DB')->schema_class,
42 traits => ['Testpostgresql']
43 },
44 'User', 'Adverts'; # only create the tables for the User and Advert Result
45 # classes
46
47As an alternative to naming specific tables in the last part of the C<use
48Test::DBIx::Class> declaration, you can use L<qw/:resultsets/> instead of
49C<'User', 'Adverts'> in the example avove, to import all Result classes
50defined in the C<schema_class>.
51
52The next thing to produce the appropriate L<Moose> meta-object incantation
53to swap out the configured C<< MyApp->model('DB')->schema >> with the
54temporary one we want to use instead (note, this works even when we start
55doing the clever override things in the next two sections):
56
57 # make TDBIC schema and app schema the same
58 my $db_meta = MyApp::Model::DB->meta;
59 $db_meta->make_mutable;
60 $db_meta->add_around_method_modifier( schema => sub { Schema() } );
61 $db_meta->make_immutable;
62
63Now that we've done this we can start making requests:
64
65 my $mech = Test::WWW::Mechanize::Catalyst->new;
66 $mech->get('whatever');
b9e2809b 67 ### etc.
61b85627 68
69And the database operations should all really happen, but to a temporary
70database that gets deleted at the end of the run. This is especially
71useful if you have lots of tests that all need a pristine copy of the
72database with their own fixtures, as it means you can speed things up by
b9e2809b 73running in parallel (e.g. to run 3 tests in parallel run C< prove -l -j 3
61b85627 74t >).
75
b9e2809b 76=head2 OK Good. This time let's optionally override the temporary database
61b85627 77with the developer's DSN
78
79One development style which works fairly well is to write tests to run on
80the development database, and then have a play around at the end of the
b9e2809b 81test run either with the Perl debugger or using the built in development
61b85627 82server. However this means that one can't always rely on having a
83temporary testing database for running tests.
84
85So in this case we use the application's configured database instead. Note
86this requires a bit more trickery than when we're just using a temporary TDBIC
87database:
88
89 use Test::More;
90
91 use FindBin qw/$Bin/;
92 use lib "$Bin/lib";
93 use Test::WWW::Mechanize::Catalyst qw/MyApp/;
94
95 BEGIN {
96 use MyApp;
97 my %tdbic_args = ( schema_class => MyApp->model('DB')->schema_class,
98 traits => [qw/Testpostgresql/],
99 );
100 if ($ENV{DEV_DB}) {
101 %tdbic_args = (
102 connect_info =>
103 MyApp->model('DB')->schema_class->storage->connect_info,
104 force_drop_table => 1,
105 keep_db => 1, # assume we want to keep the test db at
106 # the end of the test run
107 %tdbic_args
108 )
109 };
110
111 # this pattern because we're messing with instantiation in BEGIN
112 require Test::DBIx::Class;
113 Test::DBIx::Class->import(\%tdbic_args, qw/:resultsets/);
114
115=head2 Fine, that's the simple cases, what about the harder cases?
116
117In many situations it's not desirable to infer the database directly from
118the DBIC schema classes. While it is possible to put all the metadata (for
119example including stuff that requires custom database engine extensions)
120into the DBIC schema, this is not necessarily desirable. For example if you
121have a process whereby your database schemas are signed off (and likely
122modified) by a DBA you're likely going to want the master copy of your
123database in SQL rather than DBIC files. Likewise if you have evil business
b9e2809b 124logic that's best encapsulated in a database trigger, you'll likely hit the
61b85627 125same type of problems.
126
b9e2809b 127Given we're using a Postgres database in this instance, we need some
128Pg-specific code to spin up either a temporary database or to repopulate the
129development database. So to complement
130L<Test::DBIx::Class::SchemaManager::Trait::Testpostgresql>, we've written our
131own internal C<Test::DBIx::Class::SchemaManager::Trait::DeploySQL> class that
132should be kept in C< t/lib/Test/DBIx/Class/SchemaManager/Trait/DeploySQL.pm >
133in your app's directory tree. It's possible this could be released as a CPAN
134module one day, but at this stage we suspect that every development situation
135is sufficiently different that it's probably better just to leave these
136particular bits of wheel lying around for other people to adapt, rather than
137offering an explicit canned solution that's supposed to work for everybody.
138
139Meanwhile here's what we have for our Postgres database populated by SQL statements:
61b85627 140
141 use Moose::Role;
142 use MyApp;
143
144 before prepare_schema_class => sub {
145 my ($self, $schema_class) = @_;
146 { no warnings 'redefine';
147 no strict 'refs';
148 *{$schema_class.'::deploy'} = sub { $self->_deploy_sql(@_) };
149 }
150 };
151
152 sub _deploy_sql {
153 my($self, $schema) = @_;
154 my $port = $self->postgresqlobj->port;
155 my $args = $self->postgresqlobj->postmaster_args;
156 my $storage = $schema->storage;
157 my $app_root = MyApp->path_to();
158 my ($db_name) = $storage->connect_info->[0]->{dsn} =~ /dbname=(.*?)(;|$)/;
159 my ($db_user) = $storage->connect_info->[0]->{user};
160 my @sql_files = qw/list of sql files here/;
161 my $psql_cmd;
162 unless ($ENV{DEV_DB}) {
163 $psql_cmd = "/usr/bin/psql $args -p $port";
164 $storage->dbh_do(sub {
165 system qq{$psql_cmd -U$db_user $db_name -q -c "create language plpgsql"}});
166 }
167 else {
168 $psql_cmd = "/usr/bin/psql";
169 }
170 $storage->dbh_do(sub {
171 system "$psql_cmd -U$db_user $db_name -f $app_root/misc/db/$_"})
172 for @sql_files;
173 }
174
175 1;
176
177The main thing to note here is that wrapping the C<system> calls in a
178C<< $storage->dbh_do > call ensures that the database handle from DBI is
179connected to the database using the failsafe mechanisms in
180L<DBIx::Class::Schema::Storage>.
181
182So finally, deploying to our temporary database using L<Test::DBIx::Class>
183and either a temporary or a development database from SQL files is done.
184Now to start up the test file we change the traits in C<%tdbic_args> to
185this:
186
187 traits => [qw/Testpostgresql DeploySQL/],
188
189Somewhat intricate, but for complicated development situations definitely
190worth it.
191
192=head2 TODO
193
194Our development team is still working out the best way to use this system,
195but so far it's been really very handy indeed. What would be good next is
196to work out how to modularise a lot of the boilerplate above so it can be
197C<use>d or C<require>d as a single line in each test file. We'll get there
198soon.
199
200=head3 AUTHORS AND COPYRIGHT
201
202Words and a little bit of code:
203Kieren Diment <zarquon@cpan.org>
204
205Most of the code:
206Eden Cardim <edencardim@gmail.com>
207
208=head3 LICENCE
209
210This documentation can be redistributed it and/or modified under the same terms as Perl itself.