In Unit testing, you often want to mock some method of somebody else's code, i.e. you replace the method call with one that executes your own code, more appropriate to the test at hand.
This technique is called "mocking" if the faked subroutine is a class method. In the seemingly simpler case, where there are no classes and methods, only packages and subroutines, authors prefer the term "stub" to "mock", even though the idea is exactly the same.
However, creating a subroutine stub in Perl5 seems to be exceedingly difficult. All available documentation and CPAN modules refer to OO classes. Those modules that also mention stubbing (Mock::Modules, Test::Speck::Mock, and maybe others) do so only vaguely and provide no examples.
Here I present a generic solution to the problem. I create a package PackageA exporting a subroutine subA that simply prints "The Original" to stdout. The PackageB contains a subB which calls subA from PackageA, i.e. it also prints "The Original", but that's what we want to change. In a third package, called MyTest, we first call subB, then we replace the global reference of subA with our own subroutine that prints "The Stub". Finally we call subB again, but now in the stubbed version. Here is the complete example in one file:
package PackageA;
sub subA {
print "The Original\n";
}
package PackageB;
sub subB {
PackageA::subA();
}
package MyTest;
PackageB::subB();
no warnings;
local *PackageA::subA = sub {
print "The Stub\n";
};
use warnings;
PackageB::subB();
This prints:
The Original
The Stub
Good! This is exactly what we want. - Sadly it does not work if we naively split the above code across three files using the well-known Exporter.
File PackageA.pm:
package PackageA;
use Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(subA);
sub subA {
print "The Original\n";
}
1;
package PackageB;
use PackageA qw(subA);
sub subB {
subA();
}
1;
File MyTest.t (does not work):
use PackageA qw(subA);
use PackageB;
PackageB::subB();
no warnings;
local *PackageA::subA = sub {
print "The Stub\n";
};
use warnings;
PackageB::subB();
Executing perl MyTest.t prints:
The Original
The Original
No stubbing takes place, the original Package::subA is executed in both cases! How comes? - The reason is the way the Exporter/use modifies the global symbol table. Importing a subroutine does not simply create a new subroutine that calls the original one (this would be an unnecessary redirection). Importing a foreign symbol copies its reference into the own namespace. All this happens before any statements are executed, so at the time of printing, the entry *PackageA::subA still points to the original subroutine, while there is now a new entry *PackageB::subA, created by "use PackageA", that also points to the original subA, and that's the one we want to replace:
local *PackageB::subA = sub {
With this modification, the stub is actually executed: perl MyTest.t prints
The Original
The Stub
The Stub
The correct MyTest.t:
use PackageA qw(subA);
use PackageB;
PackageB::subB();
no warnings;
local *PackageB::subA = sub {
print "The Stub\n";
};
use warnings;
PackageB::subB();
I shall be happy to use a CPAN module that does the above, as soon as somebody points me to one. Until then, my unit tests will use the technique outlined here.