Using DBICx::TestDatabase with Catalyst

For testing Catalyst applications, you want to use a separate database from your production one. While you could do that with separate Catalyst config files, you'd have to take care to clean up the database at the end of every test, so that subsequent tests won't be influenced (or, alternatively, make sure you start up with a fresh database). DBICx::TestDatabase does all that for you, by creating a temporary SQLite database for the duration of the test, deploying your schema in it, then connecting to it. Here's an easy way to use DBICx::TestDatabase within your Catalyst test files.

First off,

use strict;
use warnings;
use Test::More tests => 4;

BEGIN { 
    use_ok 'Catalyst::Test''MyApp' 
    use_ok DBICx::TestDatabase;
}

We need Catalyst::Test for its uniquely useful method ctx_request, which returns the Catalyst context object, $c:

ok my ($res$c) = ctx_request('/'), 'context object';  # make sure we got the context object...
ok $res->is_success'request success';  # ...and that the request was successful

And now for the bang:

my $schema = DBICx::TestDatabase->new('MyApp::Schema');
$c->model('DBIC')->schema($schema);

That's all it takes. We told DBICx::TestDatabase to connect to our schema and to return a connection to it, then we instructed the model that we have a new schema. Now any requests we make will use the test database. Here's a test of our fictitious REST API, that will create a new entry in the test database:

my $response = request(
    POST '/api/books',
        Content_Type => 'application/json',
        Content => '{"title": "The God Delusion"}'
);

What about the old schema?

When the test starts, Catalyst will still connect to the database it finds in myapp.conf, and in a test environment, that database may not be available. An improvement would be to bypass that connection. This could be done in two ways:

  1. We could get the test database's DSN from $schema->storage->{_connect_info} (undocumented), and create a myapp_local config file with it. Catalyst::Plugin::ConfigLoader will load the _local config and override any existing settings from myapp.conf. Although this method would achieve the fastest startup, it's a bit convoluted and relies on private data from DBICx::TestDatabase.

  2. Have a myapp_test.conf config file specifying details for a test SQLite database, which will actually never be used:

    <Model::DBIC>
        connect_info = dbi:SQLite:dummy.db
        connect_info = username
        connect_info = nopassword
    </Model::DBIC>
    

    then set the environment variable MYAPP_CONFIG_LOCAL_SUFFIX to test, so that Catalyst::Plugin::ConfigLoader will load myapp_test.conf after myapp.conf1, overriding the connection information in myapp.conf. (Remember to replace 'MYAPP' in the environment variable with your application's actual name.)

    The drawback of this method is that your schema will be instantiated twice: once by Catalyst starting up and connecting to dbi:SQLite:dummy.db, and the second time by DBICx::TestDatabase. It has been speculated that this may cause a memory leak{{fact}}, although this won't be a problem in bite-sized tests.


  1. At the time of this writing, setting the environment variable doesn't work with Catalyst::Test. This bug is tracked at RT #47937. ↩

Thanks!

Buy me a coffee to sponsor more cool posts like this!

My tags:
 
Popular tags:
  Perl Catalyst DBIC