thor has asked for the wisdom of the Perl Monks concerning the following question:

Hi all,

As I alluded to here, I have a set of modules that I'm writing, one of which I need to alter the default behavior of one of the modules due to lack of a database in the testing environment. I've tried using Test::MockObject and Test::MockModule, but to no avail. It seems that what I want to do is globaly replace one module with another, so that when something tries to use it and get objects from it, objects from the mocked module are returned instead. Is this at all possible? I'd be a lot happier if it were... Also, let me know if I need to be clearer in what I'm trying to do. If needed, I can come up with some pseudo-code that should demonstrate what I would like to happen.

thor

Feel the white light, the light within
Be your own disciple, fan the sparks of will
For all of us waiting, your kingdom will come

Update: Per request, here's the pseudo-code
package Foo; use DBI; sub get_db_value { # this sub requires database connectivity }
Then, in other code:
package Bar; use Foo; sub bar_func { my $value = Foo::get_db_value(); return $value * 42; }
What I want to do is essentially replace get_db_value() with a subroutine that returns what I want it to return for testing conditions. That is to say, something like this:
use Bar; #something here to mock out Foo::get_db_value so that it returns a val +ue of "2" is(Bar::bar_func(), 84, "bar_func works as advertised");

Replies are listed 'Best First'.
Re: Testing procedure: how to mock?
by Ovid (Cardinal) on Sep 23, 2005 at 16:08 UTC

    Wy are you trying to replace one module with another? Sounds like a lot of work. Personally, when I mock something up, I try to minimize what I mock as much as possible to minimize altering the behavior. By limiting what I mock, I can know that at least the other code is getting tested.

    But if you really do want to replace one module with another, so long as they present identical interfaces, it shouldn't be too hard. However, if the module exports anything, then you have a problem. You'll need to load it and mock everything before anything else loads it.

    Cheers,
    Ovid

    New address of my CGI Course.

Re: Testing procedure: how to mock?
by dragonchild (Archbishop) on Sep 23, 2005 at 20:15 UTC
    Use DBD::Mock for mocking the connection to a database that is normally connected to with DBI and some other DBD (like mysql, Oracle, etc.)

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: Testing procedure: how to mock?
by xdg (Monsignor) on Sep 23, 2005 at 20:28 UTC

    One approach is to replace the function in Foo before Bar is compiled.

    # Compile foo use Foo; # Replace (mock) a function in Foo BEGIN { no warnings 'redefine'; *Foo::get_db_value = sub { return 2 } } # Compilation of Bar finds mocked function, not original function # (Foo was already loaded so is not recompiled) use Bar; # Test function is(Bar::bar_func(), 84, "bar_func works as advertised");

    You could replace the "2" with a package variable (or package-scoped lexical) if you want to be able to change the value returned for use in different tests.

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Re: Testing procedure: how to mock?
by stvn (Monsignor) on Sep 24, 2005 at 00:42 UTC
    thor

    I think the first question you should ask yourself is whether you need to be testing your database code. Meaning the code which utilizes DBI, and which generates SQL statements.

    If the answer is yes, I would suggest using DBD::Mock. It will allow you to mock your DBI/database interaction, and interogate the SQL being passed into it. I wrote an article on perl.com about it a little while ago, which explains the basics of using the module.

    If your answer is no, and you are not concerned with testing the SQL or DBI code, and your are not finding Test::MockObject or Test::MockModule useful, you might want to also look at Sub::Override or possibly Test::MockDBI.

    In addition, you could post some of your Test::MockObject or Test::MockModule code, and possibly someone could help you with getting it to work.

    -stvn
      In addition, you could post some of your Test::MockObject or Test::MockModule code, and possibly someone could help you with getting it to work.
      Ask, and ye shall receive. :)
      ============== Foo.pm ============== package Foo; use strict; require Exporter; our @ISA = qw(Exporter); our %EXPORT_TAGS = ( 'all' => [ qw( foo ) ] ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our $VERSION = '0.01'; sub foo { die "This is being called from Foo\n"; } 1; __END__ ============== Bar.pm ============== package Bar; use strict; use Foo qw(foo); our $VERSION = '0.01'; sub new { my ($class) = @_; my $self = []; bless($self, $class); } sub bar { my ($self) = shift; foo(); } 1; __END__ ============== Baz.pl ============== use strict; use warnings; use Test::MockModule; use Bar; { my $mock = Test::MockModule->new('Foo'); $mock->mock('foo', sub {print "Successfully mocked!\n";}); my $bar = Bar->new(); $bar->bar(); }
      Thanks in advance,

      thor

      Feel the white light, the light within
      Be your own disciple, fan the sparks of will
      For all of us waiting, your kingdom will come

        thor

        Your problem here is that your Foo module has already exported &foo into Bar's namespace by the time you have mocked anything. What you want to do is to mock Foo before loading Bar, like this:

        use strict; use warnings; use Test::MockModule; my $mock = Test::MockModule->new('Foo'); $mock->mock('foo', sub {print "Successfully mocked!\n";}); require Bar; my $bar = Bar->new(); $bar->bar();
        Personally I don't think this is ideal since you need to require Bar rather than use it.

        However, since you are exporting &Foo::foo into Bar, you could ignore the fact &Foo::foo came from Foo, and just mock &foo inside Bar. Like this:

        use strict; use warnings; use Test::MockModule; use Bar; my $mock = Test::MockModule->new('Bar'); $mock->mock('foo', sub {print "Successfully mocked!\n";}); my $bar = Bar->new(); $bar->bar();
        This too is probably not ideal.

        Of course, if you simply do not export &Foo::foo, and instead call with the fully qualitied packag name (Foo::foo()) then you original test code will work.

        -stvn
How to mock?
by Anonymous Monk on Sep 23, 2005 at 20:34 UTC
    < ObNelson > Ha, Ha! < /ObNelson >
    Maybe I've been watching too much Simpsons lately. ;-)