Sunday, August 20, 2006

Mock Classes in Perl Unit Tests

November 2, 2006: I finally released the Wrapper module described here on the CPAN system.

Today I added unit tests to a Perl module that uses the DBI module. My module, DBIx::Wrapper::VerySimple is one that I wrote years ago.

The tests use three tiny mock classes that take the place of the real DBI module. This allows the units tests to run in complete isolation from the actual DBI module.

Back when I wrote DBIx::Wrapper::VerySimple wasn't experienced enough to bother creating unit tests for my code. These days I create unit tests any time I create a new module for a project and sometimes for scripts as well.

The challenge in this case was that constructor method, DBIx::Wrapper::VerySimple->new() calls DBI->connect(), and I didn't want my unit tests to require connecting to an actual database. My solution is to have three tiny mock classes in my test code including a that the tests load before DBI::Wrapper can load the real

Here's what my mock looks like:

# Mock class - for testing only
package DBI;
use strict;
use warnings;

# warn 'Loading mock library ' . __FILE__;
my $MOCK_DBH_CLASS = 'DBI::Mock::dbh';

my %ARGS = ();

sub connect {
my ( $class, @args ) = @_;
$fake_dbh = {};
bless $fake_dbh, $MOCK_DBH_CLASS;
ARGS{$fake_dbh} = \@args;


My test script loads the mock and two other mock classes before DBI::Wrapper is loaded for testing. This way, when DBI::Wrapper is compiled my mock is used instead of the real one:

# Ensure that DBI::Wrapper loads our mock
lib "$Bin/mock_lib";
use DBI;

This approaches works very well, and I was able to create unit tests that intercept all the calls that DBI::Wrapper makes that would normally go to the real DBI, and my intercepting these I can check that DBI::Wrapper is passing the expected values to DBI. I am deliberately not testing the real DBI module which has its own extensive set of unit tests. I merely test that my module will make the expected calls to DBI.

More about DBI::Wrapper

The module is available online at
The version with the new unit tests is version 0.04.

From the README:

DBI::Wrapper is a simple module that provides a high-level interface
to the Perl DBI module. The provided methods are for fetching
a single record (returns a hash-ref), many records (returns
an array-ref of hash-refs), and for executing a non-select statement
(returns a result code).

The intention here is that your application will have much cleaner code,
so instead of writing:

$sql = 'SELECT name,address FROM $table WHERE zipcode=?';
$sth = $dbh->prepare($sql);
$rv = $sth->execute($zipcode);
while ( my $hash_ref = $sth->fetchrow_hashref ) {
push( @found_rows, $hash_ref );

You would write:

$sql = 'SELECT name,address FROM $table WHERE zipcode=?';
$found_rows = $wrapper->FetchAll($sql,$zipcode); # An array_ref of hash_refs

I wrote the my version of DBI::Wrapper several years ago after I got the idea from a co-worker when I was doing a gig at TechTV.

No comments: