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