Mocking

作者:   發佈於:  

Rspec provide this super sweet syntax sugar to write mocked methods of any given object:

player.should_receive( :run ).and_return( 42 )

Ref: http://rspec.info/documentation/mocks/message_expectations.html

I'm pondering if this could be ported to Perl.

It's not so difficult to just override the method. Suppose a Player class with a run method defined like:

package Player;
sub run {
    # calculate total distance of run...
    return $total_distance;
}

The run method can be easily overridden in anywhere by:

*{Player::run} = sub {
    return 42;
}

And it effects on all Player objects, whether they were constructed or not.

Based on this behavior, it might be possible to implement a method-mocking mechanism by:

  1. save the original code ref of Player::run
  2. replace it with the mocked version
  3. when invoked, simply returned the given return value
  4. also, restored the original Player::run method if that's desired

On the other hand, it can also be done by playing the @ISA a little bit. Suppose our goal is to mock the run method of the Player object $p:

  1. Create a MockedPlayer package with the mocked run method defined in it
  2. @{MockedPlayer::ISA} = ("Player");
  3. re-bless $p as a MockedPlayer

The downside of this approach is that the $p becomes an object of MockedPlayer class. However, the mocked run method is now only installed on the $p object, not all other Player objects as a side effect.

Based on the second approach, it might be possible to have a Mocked class with only AUTOLOAD method in it as a catch-all interface, and dispatch to either the mocked version of subroutine, or the real one, depends on how it's configured in the test program.

Here's the code synopsis of that idea:

# Construct a mocked version of Player class with a mocked "run" method
my $p = Mocked("Player", {
    run => sub { 42 }
});

# Invoke the mocked `run` method
$p->run;

# Invoke other non-mocked methods
$p->jump;

Test::MockClass and Test::MockObject has already implemented this idea. Test::MockObject also generates a isa method for the mocked object to report itself as an object of whichever mocked.

However, I still want my test program reads as simple as the rspec statement, it reads better. The most important of all, you don't really need to know the whole pros and cons and the concept of "Mocking" to do it. You just inject a method with a pre-defined return value such that it reduces the trouble when running tests.

Simple things should be easy.