package Mock::Sub::Child;
use 5.006;
use strict;
use warnings;
use Carp qw(confess);
use Scalar::Util qw(weaken);
our $VERSION = '1.09';
sub new {
my $self = bless {}, shift;
%{ $self } = @_;
if ($self->{side_effect}){
$self->_check_side_effect($self->{side_effect});
}
return $self;
}
sub _mock {
my $self = shift;
# throw away the sub name if it's sent in and we're not called
# by Mock::Sub::mock()
my $sub_passed_in;
if ($_[0] && $_[0] =~ /::/){
$sub_passed_in = 1;
}
my $caller = (caller(1))[3] || '';
if ($caller ne 'Mock::Sub::mock' && $sub_passed_in){
undef @_;
if(ref($self) eq 'Mock::Sub::Child' && ! $self->{name}){
confess "can't call mock() on a child object before it is already " .
"initialized with the parent mock object. ";
}
}
if ($caller ne 'Mock::Sub::mock' && $caller ne 'Mock::Sub::Child::remock'){
confess "the _mock() method is not a public API call. For re-mocking " .
"an existing sub in an existing sub object, use remock().\n";
}
my $sub = $self->name || shift;
my %p = @_;
for (keys %p){
$self->{$_} = $p{$_};
}
if ($sub !~ /::/) {
my $core_sub = "CORE::" . $sub;
if (defined &$core_sub && ${^GLOBAL_PHASE} eq 'START') {
warn "WARNING! we're attempting to override a global core " .
"function. You will NOT be able to restore functionality " .
"to this function.";
$sub = "CORE::GLOBAL::" . $sub;
}
else {
$sub = "main::$sub" if $sub !~ /::/;
}
}
my $fake;
if (! exists &$sub && $sub !~ /CORE::GLOBAL/) {
$fake = 1;
if (! $self->_no_warn) {
warn "\n\nWARNING!: we've mocked a non-existent subroutine. ".
"the specified sub does not exist.\n\n";
}
}
$self->_check_side_effect($self->{side_effect});
if (defined $self->{return_value}){
push @{ $self->{return} }, $self->{return_value};
}
$self->{name} = $sub;
$self->{orig} = \&$sub if ! $fake;
$self->{called_count} = 0;
{
no strict 'refs';
no warnings 'redefine';
my $mock = $self;
weaken $mock;
*$sub = sub {
@{ $mock->{called_with} } = @_;
++$mock->{called_count};
if ($mock->{side_effect}) {
if (wantarray){
my @effect = $mock->{side_effect}->(@_);
return @effect if @effect;
}
else {
my $effect = $mock->{side_effect}->(@_);
return $effect if defined $effect;
}
}
return if ! defined $mock->{return};
if ($mock->{return}[0] && $mock->{return}[0] eq 'params'){
return ! wantarray ? $_[0] : @_;
}
else {
return ! wantarray && @{ $mock->{return} } == 1
? $mock->{return}[0]
: @{ $mock->{return} };
}
};
}
$self->{state} = 1;
return $self;
}
sub remock {
shift->_mock(@_);
}
sub unmock {
my $self = shift;
my $sub = $self->{name};
{
no strict 'refs';
no warnings 'redefine';
if (defined $self->{orig} && $sub !~ /CORE::GLOBAL/) {
*$sub = \&{ $self->{orig} };
}
else {
undef *$sub if $self->{name};
}
}
$self->{state} = 0;
$self->reset;
}
sub called {
return shift->called_count ? 1 : 0;
}
sub called_count {
return shift->{called_count} || 0;
}
sub called_with {
my $self = shift;
if (! $self->called){
confess "\n\ncan't call called_with() before the mocked sub has " .
"been called. ";
}
return @{ $self->{called_with} };
}
sub name {
return shift->{name};
}
sub reset {
for (qw(side_effect return_value return called called_count called_with)){
delete $_[0]->{$_};
}
}
sub return_value {
my $self = shift;
@{ $self->{return} } = @_;
}
sub side_effect {
$_[0]->_check_side_effect($_[1]);
$_[0]->{side_effect} = $_[1];
}
sub _check_side_effect {
if (defined $_[1] && ref $_[1] ne 'CODE') {
confess "\n\nside_effect parameter must be a code reference. ";
}
}
sub mocked_state {
return shift->{state};
}
sub _no_warn {
return $_[0]->{no_warnings};
}
sub DESTROY {
$_[0]->unmock;
}
sub _end {}; # vim fold placeholder
__END__
=head1 NAME
Mock::Sub::Child - Provides for Mock::Sub
=head1 METHODS
Please refer to the C<Mock::Sub> parent module for full documentation. The
descriptions here are just a briefing.
=head2 new
This method can only be called by the parent C<Mock::Sub> module.
=head2 called
Returns bool whether the mocked sub has been called yet.
=head2 called_count
Returns an integer representing the number of times the mocked sub has been
called.
=head2 called_with
Returns a list of arguments the mocked sub was called with.
=head2 mock
This method should only be called by the parent mock object. You shouldn't be
calling this.
=head2 remock
Re-mocks an unmocked sub back to the same subroutine it was originally mocked
with.
=head2 mocked_state
Returns bool whether the sub the object represents is currently mocked or not.
=head2 name
Returns the name of the sub this object is mocking.
=head2 return_value
Send in any values (list or scalar) that you want the mocked sub to return when
called.
=head2 side_effect
Send in a code reference with any actions you want the mocked sub to perform
after it's been called.
=head2 reset
Resets all state of the object back to default (does not unmock the sub).
=head2 unmock
Restores original functionality of the mocked sub, and calls C<reset()> on the
object.
=cut
1;