Test-Ratchet
view release on metacpan or search on metacpan
lib/Test/Ratchet.pm view on Meta::CPAN
use strict;
use warnings;
package Test::Ratchet;
use Exporter::Easy ( OK => [ qw/ratchet clank/ ] );
use Data::Munge qw(rec);
use Scalar::Util qw(refaddr);
our $VERSION = '0.005';
# ABSTRACT: Mocking helper that swaps out implementations automatically
sub ratchet {
my @subrefs = @_;
my $ratchet = rec {
my $recurse = shift;
if (! @subrefs) {
die "Tried to run a ratchet but there was nothing left to do!";
}
my $now = $subrefs[0];
# simple scalar should be a number. Run the next item as a subref if
# that number is not 0. If it's reached 0, shift them both off and redo.
# Or it's an asterisk, in which case do the next subref forever.
if (not ref $now) {
if ($now eq '*') {
return $subrefs[1]->(@_);
}
if ($now > 0) {
$now = $subrefs[1];
$subrefs[0]--;
return $now->(@_);
}
else {
shift @subrefs; shift @subrefs;
# redo
return $recurse->(@_);
}
}
else {
shift @subrefs;
}
$now->(@_);
};
}
sub clank($) {
my $subref = shift;
my $caller = sprintf "%s, line %s", (caller)[1,2];
my $clank = rec { my $rec = shift; delete $Test::Ratchet::Clank::CLANK{ refaddr $rec }; &$subref };
$Test::Ratchet::Clank::CLANK{refaddr $clank} = $caller;
bless $clank, "Test::Ratchet::Clank";
}
package Test::Ratchet::Clank;
use Scalar::Util qw(refaddr);
our %CLANK;
sub DESTROY {
my $self = shift;
require Test::More;
Test::More::fail("A Clank was never run! Created at " . $CLANK{refaddr $self}) if $CLANK{ refaddr $self };
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Test::Ratchet - Mocking helper that swaps out implementations automatically
=head1 VERSION
version 0.005
=head1 SYNOPSIS
use Test::Ratchet qw(ratchet clank);
use Test::MockModule;
use Test::More;
use Some::Module;
my $mock = Test::MockModule->new('Some::Module');
$mock->mock( magic_method => ratchet(
\&first_implementation,
\&second_implementation,
# A clank *must* be run, or the test fails!
clank \&third_implementation,
));
# In reality, you will have no control over the use of this object - which
# is the purpose of the module in the first place! The actual use of this
# object would be deep in the code you are actually testing.
my $obj = Some::Module->new;
$obj->magic_method('foo'); # Returns { something => 'relevant' }
$obj->magic_method('bar'); # Returns { something => 'else' }
# This test will fail! magic_method was only run twice, but there are three
# implementations - and the third one is a clank! Failing to run a clank
( run in 1.118 second using v1.01-cache-2.11-cpan-800906f7e73 )