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