Mocking Databases

     

Any serious code that interacts with external libraries or programs has to deal with errors. In the case of database code, this is even more important. What happens when the database goes away? If your program crashes, you could lose valuable data.

Because error checking is so important, it's well worth testing. Yet none of the techniques shown so far make it easy to simulate database failures. Fortunately, there's one more trick: mock your database.

How do I do that?

InsertWrapper is a simple module that logs database connections and inserts , perhaps for diagnostics or an audit trail while developing. If it cannot connect to a database ”or if the database connection goes away mysteriously ”it cannot do its work, so it throws exceptions for the invoking code to handle.

Save the following example in your library directory as InsertWrapper.pm :

 package InsertWrapper;     use strict;     use warnings;     use DBI;     sub new     {         my ($class, %args) = @_;         my $dbh            = DBI->connect(             @args{qw( dsn user password )},             { RaiseError => 1, PrintError => 0 }         );         my $self = bless { dbh => $dbh, logfh => $args{logfh} }, $class;         $self->log( 'CONNECT', dsn => $args{dsn} );         return $self;     }     sub dbh     {         my $self = shift;         return $self->{dbh};     }     sub log     {         my ($self, $type, %args) = @_;         my $logfh                = $self->{logfh};         printf {$logfh} "[%s] %s\n", scalar( localtime(  ) ), $type;         while (my ($column, $value) = each %args)         {             printf {$logfh} "\t%s => %s\n", $column, $value;         }     }     sub insert     {         my ($self, $table, %args) = @_;         my $dbh                   = $self->dbh(  );         my $columns               = join(', ', keys %args);         my $placeholders          = join(', ', ('?') x values %args);         my $sth                   = $dbh->prepare(             "INSERT INTO $table ($columns) VALUES ($placeholders)"         );         $sth->execute( values %args );         $self->log( INSERT => %args );     }     1; 

The important tests are that connect( ) and insert( ) do the right thing when the database is present as well as when it is absent, and that they log the appropriate messages when the database calls succeed. Save the following code as insert_wrapper.t :

 #!perl     use strict;     use warnings;     use IO::Scalar;     use Test::More tests => 15;     use DBD::Mock;     use Test::Exception;     my $module      = 'InsertWrapper';     use_ok( $module ) or exit;     my $log_message = '';     my $fh          = IO::Scalar->new( $log_message );     my $drh         = DBI->install_driver( 'Mock' );     can_ok( $module, 'new' );     $drh->{mock_connect_fail} = 1;     my %args = ( dsn => 'dbi:Mock:', logfh => $fh, user => '', password => '' );     throws_ok { $module->new( %args ) } qr/Could not connect/,         'new(  ) should fail if DB connection fails';     $drh->{mock_connect_fail} = 0;     my $wrap;     lives_ok { $wrap = $module->new( %args ) }         '... or should succeed if connection works';     isa_ok( $wrap, $module );     like( $log_message, qr/CONNECT/,            '... logging connect message' );     like( $log_message, qr/\tdsn => $args{dsn}/, '... with dsn'               );     $log_message = '';     can_ok( $module, 'dbh' );     isa_ok( $wrap->dbh(  ), 'DBI::db' );     can_ok( $module, 'insert' );     $wrap->dbh(  )->{mock_can_connect} = 0;     throws_ok { $wrap->insert( 'users', name => 'Jerry', age => 44 ) }         qr/prepare failed/,         'insert(  ) should throw exception if prepare fails';     $wrap->dbh(  )->{mock_can_connect} = 1;     lives_ok { $wrap->insert( 'users', name => 'Jerry', age => 44 ) }         '... but should continue if it succeeds';     like( $log_message, qr/INSERT/,          '... logging insert message' );     like( $log_message, qr/\tname => Jerry/, '... with inserted data'     );     like( $log_message, qr/\tage => 44/,     '... for each column'        ); 

Then run it with prove :

 $  prove insert_wrapper.t  insert_wrapper....ok     All tests successful.     Files=1, Tests=15,  0 wallclock secs ( 0.22 cusr +  0.02 csys =  0.24 CPU) 

What just happened ?

One difference between InsertWrapper and the previous examples in this chapter is that this module creates its own database connection. It's much harder to intercept the call to DBI- > connect( ) without faking the module (see "Mocking Modules" in Chapter 5). Fortunately, the DBD::Mock module provides a mock object that acts as a database driver.

The test starts by setting up the testing environment and creating an IO::Scalar object that acts like a filehandle but actually writes to the $log_message variable. T



Perl Testing. A Developer's Notebook
Perl Testing: A Developers Notebook
ISBN: 0596100922
EAN: 2147483647
Year: 2003
Pages: 107

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net