File Coverage

blib/lib/Test/Mocha.pm
Criterion Covered Total %
statement 136 136 100.0
branch 30 30 100.0
condition 10 12 83.3
subroutine 42 42 100.0
pod 15 17 100.0
total 233 237 99.1


line stmt bran cond sub pod time code
1             package Test::Mocha;
2             # ABSTRACT: Test double framework with method stubs and behaviour verification
3             $Test::Mocha::VERSION = '0.66';
4              
5 13     13   1294531 use strict;
  13         125  
  13         359  
6 13     13   90 use warnings;
  13         27  
  13         360  
7              
8 13     13   67 use Carp 'croak';
  13         22  
  13         738  
9 13     13   77 use Exporter 'import';
  13         20  
  13         387  
10 13     13   68 use Scalar::Util 'blessed';
  13         25  
  13         663  
11 13     13   5712 use Test::Mocha::CalledOk::Times;
  13         33  
  13         374  
12 13     13   5365 use Test::Mocha::CalledOk::AtLeast;
  13         40  
  13         364  
13 13     13   5779 use Test::Mocha::CalledOk::AtMost;
  13         30  
  13         380  
14 13     13   5495 use Test::Mocha::CalledOk::Between;
  13         97  
  13         372  
15 13     13   5565 use Test::Mocha::Mock;
  13         54  
  13         548  
16 13     13   5995 use Test::Mocha::Spy;
  13         33  
  13         398  
17 13     13   92 use Test::Mocha::Types 'NumRange';
  13         26  
  13         94  
18 13     13   5023 use Test::Mocha::Util 'extract_method_name';
  13         29  
  13         655  
19 13     13   91 use Types::Standard qw( ArrayRef HashRef Num slurpy );
  13         28  
  13         89  
20              
21             our @EXPORT = qw(
22             mock
23             spy
24             class_mock
25             stub
26             returns
27             throws
28             executes
29             called_ok
30             times
31             atleast
32             atmost
33             between
34             verify
35             inspect
36             inspect_all
37             clear
38             SlurpyArray
39             SlurpyHash
40             );
41              
42             # croak() messages should not trace back to Mocha modules
43             $Carp::Internal{$_}++ foreach qw(
44             Test::Mocha
45             Test::Mocha::CalledOk
46             Test::Mocha::MethodStub
47             Test::Mocha::Mock
48             Test::Mocha::Spy
49             Test::Mocha::Util
50             );
51              
52             sub mock {
53 22     22 1 24012 return Test::Mocha::Mock->__new(@_);
54             }
55              
56             sub spy ($) {
57 8     8 1 1624 return Test::Mocha::Spy->__new(@_);
58             }
59              
60             sub stub (&@) {
61 67     67 1 24636 my ( $coderef, @responses ) = @_;
62              
63 67         140 foreach (@responses) {
64 66 100       431 croak 'stub() responses should be supplied using ',
65             'returns(), throws() or executes()'
66             if ref ne 'CODE';
67             }
68              
69 65         259 my @method_calls =
70             Test::Mocha::Mock->__capture_method_calls( $coderef, 'stub' );
71 57         122 for my $method_call (@method_calls) {
72             # add stub to mock
73 60         91 unshift @{ $method_call->invocant->__stubs->{ $method_call->name } },
  60         142  
74             $method_call;
75              
76             # add response to stub
77 60         235 Test::Mocha::MethodStub->cast($method_call);
78 60         90 push @{ $method_call->__responses }, @responses;
  60         129  
79             }
80 57         125 return;
81             }
82              
83             sub returns (@) {
84 42     42 1 59579 my (@return_values) = @_;
85 46     46   279 return sub { $return_values[0] }
86 42 100       261 if @return_values == 1;
87 4     4   25 return sub { @return_values }
88 4 100       39 if @return_values > 1;
89 2     4   16 return sub { }; # if @return_values == 0
90             }
91              
92             sub throws (@) {
93 18     18 1 37490 my (@exception) = @_;
94              
95             # check if first arg is a throwable exception
96 2     2   11 return sub { $exception[0]->throw }
97 18 100 100     150 if blessed( $exception[0] ) && $exception[0]->can('throw');
98              
99 16     16   130 return sub { croak @exception };
  16         1590  
100              
101             }
102              
103             sub executes (&) {
104 4     4 1 5488 my ($callback) = @_;
105 4         14 return $callback;
106             }
107              
108             ## no critic (RequireArgUnpacking,ProhibitMagicNumbers)
109             sub called_ok (&;@) {
110 121     121 1 71525 my $coderef = shift;
111              
112 121         229 my $called_ok;
113             my $test_name;
114 121 100 100     598 if ( @_ > 0 && ref $_[0] eq 'CODE' ) {
115 75         126 $called_ok = shift;
116             }
117 121 100       285 if ( @_ > 0 ) {
118 65         114 $test_name = shift;
119             }
120              
121 121         400 my @method_calls =
122             Test::Mocha::Mock->__capture_method_calls( $coderef, 'verify' );
123              
124             ## no critic (ProhibitAmpersandSigils)
125 114         217 local $Test::Builder::Level = $Test::Builder::Level + 1;
126 114   66     299 $called_ok ||= ×(1); # default if no times() is specified
127 114         312 $called_ok->( $_, $test_name ) for @method_calls;
128 114         578 return;
129             }
130             ## use critic
131              
132             ## no critic (ProhibitBuiltinHomonyms)
133             sub times ($) {
134 91     91 1 45113 my ($n) = @_;
135 91 100       262 croak 'times() must be given a number'
136             unless Num->check($n);
137              
138             return sub {
139 89     89   170 my ( $method_call, $test_name ) = @_;
140 89         367 Test::Mocha::CalledOk::Times->test( $method_call, $n, $test_name );
141 89         1718 };
142             }
143             ## use critic
144              
145             sub atleast ($) {
146 8     8 1 32400 my ($n) = @_;
147 8 100       27 croak 'atleast() must be given a number'
148             unless Num->check($n);
149              
150             return sub {
151 6     6   18 my ( $method_call, $test_name ) = @_;
152 6         31 Test::Mocha::CalledOk::AtLeast->test( $method_call, $n, $test_name );
153 6         122 };
154             }
155              
156             sub atmost ($) {
157 8     8 1 31869 my ($n) = @_;
158 8 100       35 croak 'atmost() must be given a number'
159             unless Num->check($n);
160              
161             return sub {
162 6     6   31 my ( $method_call, $test_name ) = @_;
163 6         28 Test::Mocha::CalledOk::AtMost->test( $method_call, $n, $test_name );
164 6         111 };
165             }
166              
167             sub between ($$) {
168 17     17 1 77039 my ( $lower, $upper ) = @_;
169 17 100       57 croak 'between() must be given 2 numbers in ascending order'
170             unless NumRange->check( [ $lower, $upper ] );
171              
172             return sub {
173 17     17   38 my ( $method_call, $test_name ) = @_;
174 17         69 Test::Mocha::CalledOk::Between->test( $method_call, [ $lower, $upper ],
175             $test_name );
176 13         150 };
177             }
178              
179             sub inspect (&) {
180 20     20 1 13234 my ($coderef) = @_;
181 20         87 my @method_calls =
182             Test::Mocha::Mock->__capture_method_calls( $coderef, 'inspect' );
183              
184 15         29 my @inspect;
185 15         32 foreach my $method_call (@method_calls) {
186             push @inspect,
187 61         129 grep { $method_call->__satisfied_by($_) }
188 15         21 @{ $method_call->invocant->__calls };
  15         39  
189             }
190 15         136 return @inspect;
191             }
192              
193             sub inspect_all ($) {
194 4     4 1 2723 my ($mock) = @_;
195              
196 4 100       269 croak 'inspect_all() must be given a mock or spy object'
197             if !$mock->isa('Test::Mocha::SpyBase');
198              
199 2         5 return @{ $mock->{calls} };
  2         7  
200             }
201              
202             sub clear (@) {
203 3     3 1 6839 my @mocks = @_;
204              
205 3 100       157 croak 'clear() must be given mock or spy objects'
206             if @mocks == 0;
207             croak 'clear() accepts mock and spy objects only'
208 2 100 66     4 if 0 < ( grep { !ref $_ || !$_->isa('Test::Mocha::SpyBase') } @mocks );
  3         123  
209              
210 1         4 @{ $_->__calls } = () foreach @mocks;
  2         7  
211              
212 1         3 return;
213             }
214              
215             ## no critic (NamingConventions::Capitalization)
216             sub SlurpyArray () {
217             # uncoverable pod
218 16     16 0 111 return slurpy(ArrayRef);
219             }
220              
221             sub SlurpyHash () {
222             # uncoverable pod
223 4     4 0 36 return slurpy(HashRef);
224             }
225             ## use critic
226              
227             sub class_mock {
228 3     3 1 593 my ($mocked_class) = @_;
229              
230 3         18 my $module_file = join( q{/}, split q{::}, $mocked_class ) . '.pm';
231 3         8 my $caller_pkg = caller;
232 13     13   28499 no strict 'refs'; ## no critic (TestingAndDebugging::ProhibitNoStrict)
  13         30  
  13         2658  
233              
234             # make sure the real module is not already loaded
235             croak "Package '$mocked_class' is already loaded so it cannot be mocked"
236 3 100       5 if defined ${ $caller_pkg . '::INC' }{$module_file};
  3         190  
237              
238             # check if package has already been mocked
239             croak "Package '$mocked_class' is already mocked"
240 2 100       5 if defined *{ $mocked_class . '::AUTOLOAD' }{CODE};
  2         95  
241              
242 1         4 my $mock = mock($mocked_class);
243              
244 1         5 *{ $mocked_class . '::AUTOLOAD' } = sub {
245 16     16   318 my ($method) = extract_method_name( our $AUTOLOAD );
246 16         87 $mock->$method(@_);
247 1         8 };
248 1         6 return $mock;
249             }
250              
251             1;
252              
253             __END__