File Coverage

blib/lib/Test/MockPackages/Mock.pm
Criterion Covered Total %
statement 177 177 100.0
branch 52 52 100.0
condition 31 33 93.9
subroutine 32 32 100.0
pod 6 6 100.0
total 298 300 99.3


line stmt bran cond sub pod time code
1             package Test::MockPackages::Mock;
2 6     6   77917 use strict;
  6         9  
  6         184  
3 6     6   26 use warnings;
  6         10  
  6         186  
4 6     6   1012 use utf8;
  6         17  
  6         41  
5              
6             our $VERSION = '1.00';
7              
8             =head1 NAME
9              
10             Test::MockPackages::Mock - handles mocking of individual methods and subroutines.
11              
12             =head1 VERSION
13              
14             Version 1.00
15              
16             =head1 SYNOPSIS
17              
18             my $m = Test::MockPackages::Mock->new( $package, $subroutine )
19             ->is_method()
20             ->expects( $arg1, $arg2 )
21             ->returns( 'ok' );
22              
23             =head1 DESCRIPTION
24              
25             Test::MockPackages::Mock will mock an individual subroutine or method on a given package. You most likely won't initialize new C objects directly, instead you
26             should have L create them for you using the C method.
27              
28             In short this package will allow you to verify that a given subroutine/method is: 1) called the correct number of times (see C, C, and C), 2) called with the correct arguments (see C), and 3) returns values you define (C).
29              
30             =head2 Examples
31              
32             Here's a trivial example. We have a subroutine, C that uses an external dependency, C to help calculate our value.
33              
34             sub calculate {
35             my ( $input ) = @ARG;
36              
37             return ACME::Widget::do_something( $input, 'CONSTANT' );
38             }
39              
40             When we test our C subroutine, we can mock the C call:
41              
42             subtest 'calculate()' => sub {
43             my $m = Test::MockPackages->new();
44             $m->pkg('ACME::Widget')
45             ->mock('do_something')
46             ->expects( 15, 'CONSTANT' )
47             ->returns( 20 );
48              
49             is( calculate( 15 ), 20, 'correct value returned from calculate' );
50             };
51              
52             The test output will look something like:
53              
54             ok 1 - ACME::Widget::do_something expects is correct
55             ok 2 - correct value returned from calculate
56             ok 3 - ACME::Widget::do_something called 1 time
57              
58             =cut
59              
60 6     6   337 use Carp qw(croak);
  6         11  
  6         417  
61 6     6   3042 use Const::Fast qw(const);
  6         14425  
  6         47  
62 6     6   819 use English qw(-no_match_vars);
  6         12  
  6         61  
63 6     6   3049 use Exporter qw(import);
  6         11  
  6         226  
64 6     6   5537 use Lingua::EN::Inflect qw(PL);
  6         118202  
  6         960  
65 6     6   70 use List::Util qw(max);
  6         9  
  6         622  
66 6     6   40 use Scalar::Util qw(looks_like_number weaken);
  6         8  
  6         376  
67 6     6   5301 use Storable qw(dclone);
  6         18782  
  6         623  
68 6     6   3461 use Test::Deep qw(cmp_deeply);
  6         44427  
  6         43  
69 6     6   2095 use Test::More;
  6         8040  
  6         75  
70 6     6   5187 use parent qw(Test::Builder::Module);
  6         1995  
  6         34  
71              
72             my $CLASS = __PACKAGE__;
73              
74             const my @GLOB_TYPES => qw(SCALAR HASH ARRAY HANDLE FORMAT IO);
75              
76             =head1 CONSTRUCTORS
77              
78             =head2 new( Str $package_name, Str $name )
79              
80             Instantiates a new Test::MockPackage::Mock object. C<$name> is the subroutine or method that you intend to mock in the named C<$package_name>.
81              
82             =cut
83              
84             sub new {
85 53     53 1 122329 my ( $pkg, $package_name, $name ) = @ARG;
86              
87 53         179 my $full_name = "${package_name}::$name";
88 53 100       298 my $original = exists &$full_name ? \&$full_name : undef;
89              
90 53         515 my $self = bless {
91             _allow_eval => 0,
92             _called => undef,
93             _expects => undef,
94             _full_name => $full_name,
95             _invoke_count => 0,
96             _is_method => 0,
97             _name => $name,
98             _never => 0,
99             _original_coderef => $original,
100             _package_name => $package_name,
101             _returns => undef,
102             _corrupt => 0,
103             }, $pkg;
104              
105 53         148 $self->_initialize();
106              
107 53         252 return $self;
108             }
109              
110             =head1 METHODS
111              
112             =head2 called( Int $called ) : Test::MockPackage::Mock, Throws '...'
113              
114             Will ensure that the subroutine/method has been called C<$called> times. This method cannot be used in combination with C.
115              
116             Setting C<$called> to C<-1> will prevent invocation count checks.
117              
118             You can combined this method with C and/or C to support repeated values. For example:
119              
120             $m->expects($arg1, $arg2)
121             ->expects($arg1, $arg2)
122             ->expects($arg1, $arg2)
123             ->expects($arg1, $arg2)
124             ->expects($arg1, $arg2);
125              
126             can be simplified as:
127              
128             $m->expects($arg1, $arg2)
129             ->called(5);
130              
131             By default, this package will ensure that a mocked subroutine/method is called the same number of times that C and/or C has been setup for. For example, if you call C three times, then when this object is destroyed we will ensure the mocked subroutine/method was called exactly three times, no more, no less.
132              
133             Therefore, you only need to use this method if you don't setup any expects or returns, or to simplify repeated values like what was shown up above.
134              
135             Return value: Returns itself to support the fluent interface.
136              
137             =cut
138              
139             sub called {
140 17     17 1 45 my ( $self, $called ) = @ARG;
141              
142 17 100 100     151 if ( !looks_like_number( $called ) || $called < -1 ) {
143 2         52 croak( '$called must be an integer >= -1' );
144             }
145              
146 15         30 $self->{_called} = $called;
147              
148 15         39 return $self->_validate();
149             }
150              
151             =head2 never_called() : Test::MockPackage::Mock, Throws '...'
152              
153             Ensures that this subroutine/method will never be called. This method cannot be used in combination with C, C, or C.
154              
155             Return value: Returns itself to support the fluent interface.
156              
157             =cut
158              
159             sub never_called {
160 7     7 1 27 my ( $self ) = @ARG;
161              
162 7         15 $self->{_never} = 1;
163              
164 7         22 return $self->_validate();
165             }
166              
167             =head2 is_method() : Test::MockPackage::Mock, Throws '...'
168              
169             Specifies that the mocked subroutine is a method. When setting up expectations using C, it will ignore the first value which is typically the object.
170              
171             Return value: Returns itself to support the fluent interface.
172              
173             =cut
174              
175             sub is_method {
176 5     5 1 19 my ( $self ) = @ARG;
177              
178 5         13 $self->{_is_method} = 1;
179              
180 5         14 return $self->_validate();
181             }
182              
183             =head2 expects( Any @expects ) : Test::MockPackage::Mock, Throws '...'
184              
185             Ensures that each invocation has the correct arguments passed in. If the subroutine/method will be called multiple times, you can call C multiple times. If
186             the same arguments are expected repeatedly, you can use this in conjunction with C. See L for further information.
187              
188             If you are mocking a method, be sure to call C at some point.
189              
190             When the C object goes out of scope, we'll test to make sure that the subroutine/method was called the correct number of times based on the number
191             of times that C was called, unless C is specified.
192              
193             The actual comparison is done using Test::Deep::cmp_deeply(), so you can use any of the associated helper methods to do a comparison.
194              
195             use Test::Deep qw(re);
196              
197             $m->mock( 'my_sub' )
198             ->expects( re( qr/^\d{5}\z/ ) );
199              
200             Return value: Returns itself to support the fluent interface.
201              
202             =cut
203              
204             sub expects {
205 29     29 1 2282 my ( $self, @expects ) = @ARG;
206              
207 29         40 push @{ $self->{_expects} }, \@expects;
  29         78  
208              
209 29         78 return $self->_validate();
210             }
211              
212             =head2 returns( Any @returns ) : Test::MockPackage::Mock, Throws '...'
213              
214             This method sets up what the return values should be. If the return values will change with each invocation, you can call this method multiple times.
215             If this method will always return the same values, you can call C once, and then pass in an appropriate value to C.
216              
217             When the C object goes out of scope, we'll test to make sure that the subroutine/method was called the correct number of times based on the number
218             of times that C was called, unless C is specified.
219              
220             Values passed in will be returned verbatim. A deep clone is also performed to accidental side effects aren't tested. If you don't want to have your data deep cloned, you can use returns_code.
221              
222             $m->mock('my_sub')
223             ->returns( $data_structure ); # $data_structure will be deep cloned using Storable::dclone();
224              
225             $m->mock('my_sub')
226             ->returns( returns_code { $data_structure } ); # $data_structure will not be cloned.
227              
228             If you plan on returning a L object, you will want to ensure that it's not deep cloned (using returns_code) because that module uses the object's address to keep track of mocked methods (instead of using attributes).
229              
230             C will be used to try and determine if a list or a single value should be returned. If C<@returns> contains a single element and C is false, the value at index 0 will be returned. Otherwise,
231             a list will be returned.
232              
233             If you'd rather have the value of a custom CODE block returned, you can pass in a CodeRef wrapped using a returns_code from the L package.
234              
235             use Test::MockPackages::Returns qw(returns_code);
236             ...
237             $m->expects( $arg1, $arg2 )
238             ->returns( returns_code {
239             my (@args) = @ARG;
240              
241             return join ', ', @args;
242             } );
243              
244             Return value: Returns itself to support the fluent interface.
245              
246             =cut
247              
248             sub returns {
249 30     30 1 85 my ( $self, @returns ) = @ARG;
250              
251             # dclone will remove the bless on the CodeRef.
252 30 100 66     116 if (@returns == 1 && do {
253 24         61 local $EVAL_ERROR = undef;
254 24         42 eval { $returns[ 0 ]->isa( 'Test::MockPackages::Returns' ) };
  24         246  
255             }
256             )
257             {
258 2         5 push @{ $self->{_returns} }, \@returns;
  2         7  
259             }
260             else {
261             # this should be safe since we are just doing a dclone(). According to the Storable POD, the eval is only dangerous
262             # when the input may contain malicious data (i.e. the frozen binary data).
263 28         44 local $Storable::Deparse = 1; ## no critic (Variables::ProhibitPackageVars)
264 28         34 local $Storable::Eval = 1; ## no critic (Variables::ProhibitPackageVars)
265              
266 28     1   28 push @{ $self->{_returns} }, dclone( \@returns );
  28     1   919  
  1     1   10  
  1         41  
  1         1216  
  1         5  
  1         2  
  1         38  
  1         5  
  1         2  
  1         22  
267             }
268              
269 30         106 return $self->_validate();
270             }
271              
272             # ----
273             # private methods
274             # ----
275              
276             # _initialize( ) : Bool
277             #
278             # This is where everythign is setup. We override the subroutine/method being mocked and replace it with a CodeRef
279             # that will perform the various expects checking and return values based on how returns were setup.
280             #
281             # Return value: True
282              
283             sub _initialize {
284 53     53   82 my ( $self ) = @ARG;
285              
286 53         197 my $test = $CLASS->builder;
287              
288 53         398 weaken $self;
289             my $mock = sub {
290 57     57   1910 my ( @got ) = @ARG;
291              
292             # used for returns_code
293 57         83 my @original_args = @got;
294              
295             # _invoke_count keeps track of how many times this subroutine/method was called
296 57         108 my $invoke_number = ++$self->{_invoke_count};
297              
298             # $i is the current invocation
299 57         79 my $i = $invoke_number - 1;
300              
301             # The first value passed into the method is the object itself. Ignore that.
302 57 100       135 if ( $self->{_is_method} ) {
303 6         11 shift @got;
304             }
305              
306             # setup the expectations
307 57 100       127 if ( my $expects = $self->{_expects} ) {
308 27         33 my $n_expects = scalar( @$expects );
309 27         26 my $expected;
310 27 100 100     126 if ( $n_expects == 1 && defined( $self->{_called} ) ) {
    100          
311 6         9 $expected = $expects->[ 0 ];
312             }
313             elsif ( $i >= $n_expects ) {
314             croak(
315             sprintf(
316             '%s was called %d %s. Only %d %s defined',
317 1         44 $self->{_full_name}, $invoke_number, PL( 'time', $invoke_number ),
318             $n_expects, PL( 'expectation', $n_expects )
319             )
320             );
321             }
322             else {
323 20         39 $expected = $expects->[ $i ];
324             }
325              
326 26         41 local $Test::Builder::Level = $Test::Builder::Level + 1; ## no critic (Variables::ProhibitPackageVars)
327 26         126 cmp_deeply( \@got, $expected, "$self->{_full_name} expects is correct" );
328             }
329              
330             # setup the return values
331 56         56175 my @returns;
332 56 100       133 if ( my $returns = $self->{_returns} ) {
333 30         37 my $n_returns = scalar @$returns;
334              
335 30 100 100     167 if ( $n_returns == 1 && defined( $self->{_called} ) ) {
    100          
336 9         11 @returns = @{ $returns->[ 0 ] };
  9         26  
337             }
338             elsif ( $i >= $n_returns ) {
339             croak(
340             sprintf(
341             '%s was called %d %s. Only %d %s defined',
342 2         8 $self->{_full_name}, $invoke_number, PL( 'time', $invoke_number ),
343             $n_returns, PL( 'return', $n_returns )
344             )
345             );
346             }
347             else {
348 19         34 @returns = @{ $returns->[ $i ] };
  19         63  
349             }
350             }
351             else {
352 26         98 return;
353             }
354              
355 28 100 66     86 if (@returns == 1 && do {
356 22         30 local $EVAL_ERROR = undef;
357 22         34 eval { $returns[ 0 ]->isa( 'Test::MockPackages::Returns' ) };
  22         253  
358             }
359             )
360             {
361 2         11 return $returns[ 0 ]->( @original_args );
362             }
363              
364             # return the first element if only one return defined and a wantarray is false.
365 26 100 100     227 return !wantarray && scalar( @returns ) == 1 ? $returns[ 0 ] : @returns;
366 53         315 };
367              
368 53         70 do {
369 6     6   5777 no strict qw(refs); ## no critic (TestingAndDebugging::ProhibitNoStrict)
  6         15  
  6         223  
370 6     6   27 no warnings qw(redefine); ## no critic (TestingAndDebugging::ProhibitNoWarnings)
  6         9  
  6         2053  
371 53         109 my $full_name = $self->{_full_name};
372 53         230 *$full_name = $mock;
373             };
374              
375 53         88 return 1;
376             }
377              
378             # _validate( ) Test::MockPackages::Mock, Throws '...'
379             #
380             # Validates that the mock has been properly configured up to this point. If any errors
381             # were detected, raise an exception.
382             #
383             # Return value: Returns itself to support the fluent interface.
384              
385             sub _validate {
386 86     86   132 my ( $self ) = @ARG;
387              
388 86         137 my $called = $self->{_called};
389 86         98 my $never = $self->{_never};
390 86 100       164 my $n_expects = $self->{_expects} ? @{ $self->{_expects} } : 0;
  37         52  
391 86 100       170 my $n_returns = $self->{_returns} ? @{ $self->{_returns} } : 0;
  35         53  
392              
393             # called of -1 will be allowed with multiple expects and/or returns. Any other value of called will require that expects or returns
394             # has only been defined 0 or 1 time.
395 86 100 100     289 if ( defined( $called ) && $called >= 0 ) {
396              
397             # breaking into two if statements so Devel::Cover marks this condition as covered
398 22 100 100     110 if ( $n_expects > 1 || $n_returns > 1 ) {
399 2         4 $self->{_corrupt} = 1;
400 2         40 croak( 'called() cannot be used if expects() or returns() have been defined more than once' );
401             }
402             }
403              
404 84 100       149 if ( $never ) {
405              
406             # breaking into two if statements so Devel::Cover marks this condition as covered
407 10 100 100     112 if ( $called || $n_expects || $n_returns ) {
      100        
408 3         5 $self->{_corrupt} = 1;
409 3         53 croak( 'never_called() cannot be used if called(), expects(), or returns() have been defined' );
410             }
411             }
412              
413 81         309 return $self;
414             }
415              
416             # _expected_invocations( ) : Maybe[Int]
417             #
418             # Calculates how many times a subroutine/method is expected to be called.
419             #
420             # Return value: an integer value on the number of times the subroutine/method should be called.
421              
422             sub _expected_invocations {
423 53     53   68 my ( $self ) = @ARG;
424              
425 53 100       135 return 0 if $self->{_never};
426              
427 46 100       127 if ( defined( my $called = $self->{_called} ) ) {
428 14 100       47 if ( $called == -1 ) {
429 1         3 return;
430             }
431              
432 13         24 return $called;
433             }
434              
435 32 100       75 my $n_expects = $self->{_expects} ? @{ $self->{_expects} } : 0;
  14         30  
436 32 100       66 my $n_returns = $self->{_returns} ? @{ $self->{_returns} } : 0;
  17         35  
437 32         98 my $max = max( $n_expects, $n_returns );
438              
439 32 100       94 return $max >= 1 ? $max : undef;
440             }
441              
442             # DESTROY( )
443             #
444             # DESTROY is used to the original subroutine/method back into place and perform any final expectation checking.
445              
446             sub DESTROY {
447 6     6   38 no strict qw(refs); ## no critic (TestingAndDebugging)
  6         13  
  6         257  
448 6     6   28 no warnings qw(redefine); ## no critic (TestingAndDebugging)
  6         10  
  6         1446  
449              
450 53     53   8944 my ( $self ) = @ARG;
451              
452 53         95 my $full_name = $self->{_full_name};
453              
454 53         111 my $expected_invocations = $self->_expected_invocations;
455 53 100 100     286 if ( !$self->{_corrupt} && defined $expected_invocations ) {
456 41         73 local $Test::Builder::Level = $Test::Builder::Level + 6; ## no critic (Variables::ProhibitPackageVars)
457             $CLASS->builder->is_num( $self->{_invoke_count},
458 41         152 $expected_invocations,
459             sprintf( '%s called %d %s', $full_name, $expected_invocations, PL( 'time', $expected_invocations ) ) );
460             }
461              
462             # if we have an original CodeRef, put it back in place.
463 53 100       31696 if ( my $original = $self->{_original_coderef} ) {
464 42         590 *$full_name = $original;
465             }
466              
467             # otherwise, remove the CodeRef from the symbol table, but make sure the other types are
468             # left intact.
469             else {
470 11         17 my %copy;
471 11         30 $copy{$ARG} = *$full_name{$ARG} for grep { defined *$full_name{$ARG} } @GLOB_TYPES;
  66         192  
472 11         167 undef *$full_name;
473 11         66 *$full_name = $copy{$ARG} for keys %copy;
474             }
475              
476 53         440 return;
477             }
478              
479             1;
480              
481             __END__