[Catalyst] Testing--best-practice help (long; will wiki the responses)

Jesse Sheidlower jester at panix.com
Wed Jun 17 10:58:49 GMT 2009

Back in April I posted for help here, asking specifically how
to use a test database with Test::WWW::Mechanize::Catalyst. I
am grateful for the helpful responses I got, and apologize
that it took me so long to start working on them. My recent
switch from CVS to git has enabled me to get going on lots of
things, now that I can reorganize and rename without fear of
losing history.

With a lot of help from kd, I have gotten a basic setup going,
and while it works, there are various things I specifically
don't like and various more that I probably need to be told
are bad. So, I'd be grateful for any pointers, and will post
my final setup to the wiki. 

Code will be pasted below. 

My basic setup is a MySQL database and a not-that-unusual
DBIC-based Cat app, with login, CRUD functionality (requiring
various levels of authz), some graphical output, e-mailing,
etc. I mainly would like to run TWAM-based tests of the
working app; I'd also be interested in running other tests, of
the actual components, but since I've never quite understood
how to do this, it's not my main priority (my main priority is
making sure the app works).

Originally, various people suggested using environment
variables to declare whether testing was happening, but in
retrospect I didn't see the need for this; the test script
knows it's testing so it can just use the testing config and
database. So, I have a test database that is just one of my
regular backups, and a config file that is my regular config
file with the database connection info changed to the test

I have two files right now, t/test_infrastructure.t, which is
my actual test file, and t/lib/test_setup.pl, which creates
the database from my backup, sets config info, and deletes the
database when I'm done. (The t/lib/ directory is used for the
support files.)

Here are some specific questions:

* Is there any solution to using %ENV for global variables in
the setup script? I need the same info to be available in the
BEGIN and the END blocks; using "our" in the BEGIN doesn't
work. %ENV looks ugly and feels wrong.

* Is there any alternative to the ugliness of stuff like

  $ENV{USER_NAME} = $config{'Model::LibraryDB'}->{'connect_info'}->[1];

to get the data I need from the config file? That's where this
info is in that file, but this looks horrific. Similarly, how
do I get the database name from the config file? It's there,
but only included in the query string:

	schema_class Library::Schema::Main
	connect_info DBI:mysql:host=localhost;database=library_test
	connect_info library_test
	connect_info library_test
		mysql_enable_utf8 1

I believe this is the usual form for such config files, but if
I need the database name separately, as I do here....

* How do I properly test the database creation (or is it even
necessary to do so--if it doesn't work, the tests won't run)?
I know that wrapping system calls in "ok" won't necessarily
work because the return values aren't predictable.

Alternately, should I create and delete the database in a
different way? I am concerned about speed; some of my
databases are large enough that I think I do need the fastest
way to load them, which is mysql from the commandline. BTW I
did look at Fixtures, as some people suggested, but it seemed
unnecessary for my needs here, unless I was misunderstanding

* Currently this will only work with one big test file. But
I'm sure I'd want to have multiple test files, either breaking
the mech testing up just for convenience (because the app is
large), or doing real non-mech unit tests in separate files.
How can I set this up such that the database is created before
the tests, and deleted after the tests? And if the answer is
to put the database creation in the first file and the
deletion in the last, what if I just wanted to run a single
test file (but still need the database to be there for it)?

I'm open to other methods or locations for the
creation/deletion stuff; originally I had it all in the same
file as the test, but kd suggested putting it in a file in
lib/ to keep setup separate from testing. (Of course, keeping
it in the same file also doesn't help if the goal is to have
separate test files that can work on the same database.)

* Finally (for now) I note that when I run the file below with
"prove -v --lib lib t/test_infrastructure.t", it does pass all
the tests, but I get an error of "Use of uninitialized value
$buffer in concatenation (.) or string at
/usr/share/perl5/Catalyst/Engine/CGI.pm line 220" at the
$mech->get_ok("/") call.

Thanks very much....


--- t/test_infrastructure.t
use strict;
use warnings;

use Test::More qw/no_plan/;
use FindBin qw/$Bin/;
require "$Bin/lib/test_setup.pl";

use Test::WWW::Mechanize::Catalyst;

ok( my $mech = Test::WWW::Mechanize::Catalyst->new(catalyst_app => 'Library'), "
Created mech object" );

$mech->get_ok("/", "Hits root page");

--- t/lib/test_setup.pl
use strict;
use warnings;


  # this is identical to regular conf file, but connects to test db

  our $config_file = "$Bin/lib/library_test.conf";
  our $test_database = "$Bin/lib/library_test_db.sql";

  use Config::General;
  our %config = Config::General->new($config_file)->getall;

  $ENV{USER_NAME} = $config{'Model::LibraryDB'}->{'connect_info'}->[1];
  $ENV{PASSWORD} = $config{'Model::LibraryDB'}->{'connect_info'}->[2];

  # how to set "library_test" as name for the database,
  # while keeping format of existing config file?
  $ENV{DATABASE_NAME} = "library_test";

  # assumes there's a MySQL user library_test with rights to
  # library_test database

  system("mysqladmin -u $ENV{USER_NAME} -p$ENV{PASSWORD} create $ENV{DATABASE_NAME}");
  system("mysql -u $ENV{USER_NAME} -p$ENV{PASSWORD} $ENV{DATABASE_NAME} < $test_database");

  $ENV{CATALYST_CONFIG} = $config_file;

  system("mysqladmin -u $ENV{USER_NAME} -p$ENV{PASSWORD} --force drop $ENV{DATABASE_NAME}");


More information about the Catalyst mailing list