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