App-SimpleBackuper
view release on metacpan or search on metacpan
local/lib/perl5/Test/Spec/Mocks.pm view on Meta::CPAN
package Test::Spec::Mocks;
use strict;
use warnings;
use Carp ();
use Scalar::Util ();
use Test::Deep::NoTest ();
require Test::Spec;
our @EXPORT_OK = qw(stubs stub expects mock);
our @EXPORT = @EXPORT_OK;
our $Debug = $ENV{TEST_SPEC_MOCKS_DEBUG};
our %To_Universal = map { $_ => 1 } qw(stubs expects);
#
# use Test::Spec::Mocks (); # nothing (import never called)
# use Test::Spec::Mocks; # stubs,expects=>UNIVERSAL, stub,mock=>caller
# use Test::Spec::Mocks qw(stubs stub); # stubs=>UNIVERSAL, stub=>caller
#
sub import {
my $srcpkg = shift;
my $callpkg = caller(0);
my @syms = @_ ? @_ : @EXPORT;
SYMBOL: for my $orig_sym (@syms) {
no strict 'refs';
# accept but ignore leading '&', we only export subs
(my $sym = $orig_sym) =~ s{\A\&}{};
if (not grep { $_ eq $sym } @EXPORT_OK) {
Carp::croak("\"$orig_sym\" is not exported by the $srcpkg module");
}
my $destpkg = $To_Universal{$sym} ? 'UNIVERSAL' : $callpkg;
my $src = join("::", $srcpkg, $sym);
my $dest = join("::", $destpkg, $sym);
if (defined &$dest) {
if (*{$dest}{CODE} == *{$src}{CODE}) {
# already exported, ignore request
next SYMBOL;
}
else {
Carp::carp("Clobbering existing \"$orig_sym\" in package $destpkg");
}
}
*$dest = \&$src;
}
}
# Foo->stubs("name") # empty return value
# Foo->stubs("name" => "value") # static return value
# Foo->stubs("name" => sub { "value" }) # dynamic return value
sub stubs {
_install('Test::Spec::Mocks::Stub', @_);
}
# Foo->expects("name") # empty return value
sub expects {
if (@_ != 2 || ref($_[1])) {
Carp::croak "usage: ->expects('foo')";
}
_install('Test::Spec::Mocks::Expectation', @_);
}
sub _install {
my $stub_class = shift;
my ($caller) = ((caller(1))[3] =~ /.*::(.*)/);
my $target = shift;
my @methods;
# normalize name/value pairs to name/subroutine pairs
if (@_ > 0 && @_ % 2 == 0) {
# list of name/value pairs
while (my ($name,$value) = splice(@_,0,2)) {
push @methods, { name => $name, value => $value };
}
}
elsif (@_ == 1 && ref($_[0]) eq 'HASH') {
# hash ref of name/value pairs
my $args = shift;
while (my ($name,$value) = each %$args) {
push @methods, { name => $name, value => $value };
}
}
elsif (@_ == 1 && !ref($_[0])) {
# name only
push @methods, { name => shift };
}
else {
Carp::croak "usage: $caller('foo'), $caller(foo=>'bar') or $caller({foo=>'bar'})";
}
my $context = Test::Spec->current_context
|| Carp::croak "Test::Spec::Mocks only works in conjunction with Test::Spec";
my $retval; # for chaining. last wins.
for my $method (@methods) {
my $stub = $stub_class->new({ target => $target, method => $method->{name} });
$stub->returns($method->{value}) if exists $method->{value};
$context->on_enter(sub { $stub->setup });
$context->on_leave(sub { $stub->teardown });
$retval = $stub;
}
return $retval;
}
# $stub_object = stub();
# $stub_object = stub(method => 'result');
# $stub_object = stub(method => sub { 'result' });
sub stub {
my $args;
if (@_ % 2 == 0) {
$args = { @_ };
}
elsif (@_ == 1 && ref($_[0]) eq 'HASH') {
$args = shift;
}
else {
Carp::croak "usage: stub(%HASH) or stub(\\%HASH)";
}
my $blank = _make_mock();
$blank->stubs($args) if @_;
return $blank;
}
# $mock_object = mock(); $mock_object->expects(...)
sub mock {
Carp::croak "usage: mock()" if @_;
return _make_mock();
}
{
package Test::Spec::Mocks::MockObject;
# this page intentionally left blank
}
# keep this out of the MockObject class, so it has a blank slate
sub _make_mock {
return bless({}, 'Test::Spec::Mocks::MockObject');
}
{
package Test::Spec::Mocks::Expectation;
sub new {
my $class = shift;
my $self = bless {}, $class;
# expect to be called exactly one time in the default case
( run in 2.886 seconds using v1.01-cache-2.11-cpan-5a3173703d6 )