Perl.com 
 Published on Perl.com http://www.perl.com/pub/a/2005/04/07/mockobject_kata.html
See this if you're having trouble printing code examples

 

Perl Code Kata: Mocking Objects
By Stevan Little
April 07, 2005

The last Perl Code Kata was on DBD::Mock, a mock DBI driver which is useful for testing Perl DBI applications. This Kata delves once again into the world of mock objects, this time using the more general Test::MockObject module.

What are Mock Objects?

Mock objects are exactly what they sound like: "mocked" or "fake" objects. Through the power of polymorphism, it's easy to swap one object for another object which implements the same interface. Mock objects take advantage of this fact, allowing you to substitute the most minimally mocked implementation of an object possible for the real one during testing. This allows a greater degree of isolation within your tests, which is just an all around good thing.

What are Mock Objects Good For?

Mock objects are primarily useful when writing unit tests. They share a certain similarity with the Null Object pattern in that they are purposefully not meant to work. Mock objects take things one step further and allow you to mock certain actions or reactions that your mock object should have, so they are especially useful in scenarios usually considered hard to test. Here is a short list of some scenarios in which mock objects make hard things easy.

The Problem

The example code for this kata illustrates as many points as possible about which mock objects are good at testing. Here is the code:

package Site::Member;

use strict;
our $VERSION = '0.01';

sub new { bless { ip_address => '' }, shift }

sub ip_address { 
    my ($self, $ip_address) = @_;
    $self->{ip_address} = $ip_address if $ip_address;
    return $self->{ip_address};
}

# ...

sub city {
    my ($self) = @_;
    eval "use Geo::IP";
    if ($@) {
        warn "You must have Geo::IP installed for this feature";
        return;
    }
    my $geo = Geo::IP->open(
                "/usr/local/share/GeoIP/GeoIPCity.dat", 
                Geo::IP->GEOIP_STANDARD
            ) || die "Could not create a Geo::IP object with City data";
    my $record = $geo->record_by_addr($self->ip_address());
    return $record->city();
}

This example code comes from a fictional online community software package. Many such sites offer user homepages which can display all sorts of user information. As an optional feature, the software can use the member's IP address along with the Geo::IP module to determine the user's city. The reason this feature is optional is that while Geo::IP and the C library it uses are both free, the city data is not.

The use cases suggest testing for the following scenarios:

Using Test::MockObject, take thirty to forty minutes and see if you can write tests which cover all these use cases.

Tips, Tricks, and Suggestions

Some of the real strengths of Test::MockObject lie in its adaptability and how simply it adapts. All Test::MockObject sessions begin with creating an instance.

my $mock = Test::MockObject->new();

Even just this much can be useful because a Test::MockObject instance warns about all un-mocked methods called on it. I have used this "feature" to help trace calls while writing complex tests.

The next step is to mock some methods. The simplest approach is to use the mock method. It takes a method name and a subroutine reference. Every time something calls that method on the object, your $mock instance will run that sub.

$mock->mock('greetings' => sub {
    my ($mock, $name) = @_;
    return "Hello $name";
});

How much simpler could it be?

Test::MockObject also offers several pre-built mock method builders, such as set_true, set_false, and set_always. These methods pretty much DWIM.

$mock->set_true('foo'); # the foo() method will return true
$mock->set_false('bar'); # the bar() method will return false
$mock->set_always('baz' => 100); # the bar() method will always return 100

It's even possible for the object to mock not only the methods, but its class as well. The simplest approach is to use the set_isa method to tell the $mock object to pretend that it belongs to another class.

$mock->set_isa('Foo::Bar');

Now, any code that calls this mock object's isa() method will believe that the $mock is a Foo::Bar object.

In many cases, it is enough to substitute a $mock instance for a real one and let polymorphism do the rest. Other times it is necessary to inject control into the code much earlier than this. This is where the fake_module method comes in.

With the fake_module method, Test::MockObject can subvert control of an entire package such that it will intercept any calls to that package. The following code:

my $mock = Test::MockObject->new();
$mock->fake_module('Foo::Bar' => (
    'import' => sub { die "Foo::Bar could not be loaded" }
));
use_ok('Foo::Bar');

...actually gives the illusion that the Foo::Bar module failed to load regardless of whether the user has it installed. These kinds of edge cases can be very difficult to test, but Test::MockObject simplifies them greatly.

But wait, that's not all.

After your tests have run using your mock objects, it is possible to inspect the methods called on them and query the order of their calls. You can even inspect the arguments passed into these methods. There several methods for this, so I refer you to the POD documentation of Test::MockObject for details.

by Stevan Little

The Solution

I designed each use case to illustrate a different capability of Test::MockObject.

Conclusion

Mock objects can seem complex and overly abstract at first, but once grasped they can be a simple, clean way to make hard things easy. I hope to have shown how creating simple and minimal mock object with Test::MockObject can help in testing cases which might be difficult using more traditional means.

Perl.com Compilation Copyright © 1998-2006 O'Reilly Media, Inc.