It's Easy To Mock

Recently there were cries in the office of:

oh noes! $thingy, which we rely on but don’t control, no longer has a hard-coded, specific record upon which we rely for one of our application tests

Miraculously I had a free slot in my calendar, donned my cape, took off my horn-rimmed glasses and wore my underpant outside my jeans:

I’ll have a look at that for you guys!

Buried Deeply … Somewhere

It turns out that our test was distanced from the problematic remote call by at least one intermediate “helper” module:

our_test.t:

# ...
use Test::OurApp::Data;

# ...
my $thing = Test::OurApp::Data
    ->get_thingy_from_xml( $file );

Test/OurApp/Data.pm:

# ...
use OurApp::ThingyImporter;

# ...
sub get_thingy_from_xml {
    # ...
    $result = import_thingy_ok(
        # ...
    );
    # ...
}

# ...
sub get_thingy_from_xml {
    # ...
    $result = OurApp::ThingyImporter::process_thingy_xml(
        # ...
    );
    # ...
}

OurApp/ThingyImporter.pm:

# ...
sub process_thingy_xml {
    # ...
    $service = OurApp::Domain::Helper->new(
        # ...
    );
    $result = $service->our_annoying_method(
        # ...
    );
    # ...
}

Don’t spend too long worrying about the exact path through the code, just notice that our test is a long way from our_annoying_method()

There wasn’t an obvious or easy way to alter the code to say

We’re testing something else, we don’t care too much about the results

without evil things like test_mode => 1 being passed through the code-tree. Messy and horrible.

Mocking

I’ve heard about mocking objects over the years but have never found a case for using it … until now.

I decided it was time for another look at Test::MockObject.

A short investigation revealed that our_annoying_method() returned a hashref of data used by the rest of the tests … and the tests didn’t care about most of the values.

All I needed to do was inject a method that subverted the problematic method call.

The Solution

The solution was far easier than I anticipated. I wrote a new module, living under t/lib

package Test::OurApp::Mock::Thingy;
use Moose;
    extends 'OurApp::Domain::Thingy';

use Test::MockObject;

# override the troublesome method - we don't really need
# to worry about actual lookups, we just need some data to work with
sub our_annoying_method {
    my $self    = shift;
    my $data    = shift;

    # we don't really care what's returned
    # as long as it has valid looking data
    my $result = {
        requiredData        => $data->{reference}||'CARROT';
        # ...
    };

    return $result;
}

# use Test::MockObject to inject our own method
my $mock = Test::MockObject->new;
$mock->fake_module(
    'OurApp::Domain::Thingy',
    our_annoying_method => \&our_annoying_method,
);

1;

All that was then required was one extra line early in our troubled tests sctipt:

use Test::OurApp::Mock::Thingy;

Tests no longer rely on specific data in someone else’s remote service. Tests pass. Developers happy. Success.

Leave a comment

About Chisel

user-pic I blog about Perl.