The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Test::MockDBI - Mock DBI interface for testing

SYNOPSIS

  use Test::MockDBI;
     OR
  use Test::MockDBI qw( :all );

  Test::MockDBI::set_dbi_test_type(42);
  if (Test::MockDBI::get_dbi_test_type() == 42) {
    ...

  $mock_dbi = get_instance Test::MockDBI;

  $mock_dbi->bad_method(
   $method_name,
   $dbi_testing_type,
   $matching_sql);

  $mock_dbi->bad_param(
   $dbi_testing_type,
   $param_number,
   $param_value);

  $mock_dbi->set_retval_array(
   $dbi_testing_type,
   $matching_sql,
   @retval || CODEREF);
  $mock_dbi->set_retval_array(MOCKDBI_WILDCARD, ...

  $mock_dbi->set_retval_scalar(
   $dbi_testing_type,
   $matching_sql,
   $retval || CODEREF);
  $mock_dbi->set_retval_scalar(MOCKDBI_WILDCARD, ...

  $mock_dbi->set_rows(
   $dbi_testing_type,
   $matching_sql,
   $rows || CODEREF);
  $mock_dbi->set_rows(MOCKDBI_WILDCARD, ...

EXAMPLE

Code:

  # Enable testing with Test::MockDBI
  BEGIN { push @ARGV, "--dbitest"; }
  use Test::MockDBI qw( :all );
  my $md  = Test::MockDBI::get_instance();
  my $dbh = DBI->connect("", "", "");

  # Set of return values for given sql query
  my $aref_of_hrefs = [
    { name => 'Huey',  instrument => 'cello' },
    { name => 'Dewey', instrument => 'trombone' },
    { name => 'Louie', instrument => 'piano' },
  ];
  $md->set_retval_scalar(
    MOCKDBI_WILDCARD,
    "select name, instrument from nephews",
    sub { shift @$aref_of_hrefs }
  );

  # Execute the sql query and fetch results
  $dbh->prepare("select name, instrument from nephews");
  while (my $href = $dbh->fetchrow_hashref()) {
    print $href->{name} .
          " plays the " .
          $href->{instrument} . "\n";
  }
  __END__

Expected output:

  Huey plays the cello
  Dewey plays the trombone
  Louie plays the piano

DESCRIPTION

Test::MockDBI provides a way to test DBI interfaces by creating rules for changing the DBI's behavior, then examining the standard output for matching patterns.

Testing using Test::MockDBI is enabled by setting the DBI testing type to a non-zero value. This can be done either by using a first program argument of "--dbitest[=TYPE]", or by using the class method Test::MockDBI::set_dbi_test_type(). (Supplying a first argument of "--dbitest[=TYPE]" often works well during testing.) TYPE is a simple integer (/^\d+$/). Supplying "--dbitest[=TYPE]" as a first argument works even if no other command-line processing is done, as Test::MockDBI does its own command-line processing to check for this first "--dbitest[=TYPE]" argument. You will want to add "--dbitest[=TYPE]" during a BEGIN block before the "use Test::MockDBI", so that the mock DBI is initialized as early as possible.

TYPE is optional, as a first argument of "--dbitest" will set the DBI testing type to 1 (one). DBI testing is also disabled by "--dbitest=0" (although this may not be generally useful). The class method Test::MockDBI::set_dbi_test_type() can also be used to set or change the DBI testing type.

When DBI testing is disabled, DBI is used as you would expect. This makes using Test::MockDBI transparent to your users.

The one exportable constant is:

MOCKDBI_WILDCARD

MOCKDBI_WILDCARD is the wildcard DBI testing type ("--dbitest=TYPE"), used when the fetch*() functions should always return the same value no matter what DBI testing type has been set.

External methods are:

get_dbi_test_type()

Returns the numeric DBI test type. The type is 0 when not testing the DBI interface.

set_dbi_test_type()

Sets the numeric DBI test type. The type is set to 0 if the argument cannot be interpreted as a simple integer digit string (/^\d+$/).

bad_method()

For the DBI method $method_name, when the DBI testing type is $dbi_testing_type and the current SQL matches the regex pattern in the string $matching_sql, make the function _fail (usually by returning undef).

bad_param()

When the DBI testing type is $dbi_testing_type, make the fetch*() functions fail if one of their corresponding bind_param()s has parameter number $param_number with the value $param_value.

set_retval_array()

When the DBI testing type is $dbi_testing_type and the current SQL matches the pattern in the string $matching_sql, fetch() and fetchrow_array() return the contents of the array @retval. If retval is actually a CODEREF, the array returned from calling that subroutine will be returned instead.

set_retval_scalar()

When the DBI testing type is $dbi_testing_type and the current SQL matches the pattern in the string $matching_sql, fetchall_arrayref(), fetchrow_arrayref(), fetchall_hashref(), fetchrow_hashref(), and fetchrow() return the scalar value $retval . If retval is actually a CODEREF, the scalar returned from calling that subroutine will be returned instead .

set_rows()

When the DBI testing type is $dbi_testing_type and the current SQL matches the pattern in the string $matching_sql, rows() returns the scalar value $rows. If retval is actually a CODEREF, the scalar returned from calling that subroutine will be returned instead.

set_errstr()

Allows errstr to be set and unset at runtime.

get_instance()

Returns the Test::MockDBI instance. This is a singleton. Will print debug messages to stdout if given a defined argument.

NOTES

A good source of Test::MockDBI examples is how the t/*.t test programs works.

bad_method() forces developers to use a different DBI testing type ("--dbitest=TYPE") for each different SQL pattern for a DBI method. This can be construed as a feature. (The workaround to this feature is to use MOCKDBI_WILDCARD.)

DBI fetch() and fetchrow_array() will return the undef value if the specified return value is a 1-element array with undef as the only element. I don't think this should prove a major obstacle in testing. It was coded this way due to how Perl currently handles a return value of undef when an array is expected, which is a one-element array with undef as the only element.

MOCKDBI_WILDCARD is only supported for the fetch*() return value setting methods, set_retval_scalar() and set_retval_array(). It probably does not make sense for the other external methods, as they are for creating DBI failures (and how often do you want your code to fail for all DBI testing types?)

If for some strange reason you should be installing Test::MockDBI into a system with DBI but without any DBD drivers (apart from DBD drivers bundled with DBI), you can use: perl samples/DBD-setup.pl cp samples/DBI.cfg . to create a sample DBM database (zipcodes.*) for testing Test::MockDBI (DBD::DBM ships with DBI).

DBI fetchrow() is supported, although it is so old it is no longer documented in the mainline DBI docs.

SEE ALSO

DBI, Test::MockObject::Extends, Test::Simple, Test::More, perl(1)

DBD::Mock (another approach to testing DBI applications)

DBI trace() (still another approach to testing DBI applications)

IO::String (for capturing standard output)

CAVEAT

fetch*_hashref does not allow modification of returned data set.

This means you must copy-by-value if you wish to modify the data before returning to the calling client.

AUTHOR

Mark Leighton Fisher, <mark-fisher@fisherscreek.com>

Minor modifications (version 0.62 onwards) by Andreas Faafeng <aff@cpan.org>

COPYRIGHT

Copyright 2004, Fisher's Creek Consulting, LLC. Copyright 2004, DeepData, Inc.

LICENSE

This code is released under the same licenses as Perl itself.