File Coverage

blib/lib/Test/Mock/Wrapper.pm
Criterion Covered Total %
statement 221 234 94.4
branch 52 64 81.2
condition 16 31 51.6
subroutine 48 50 96.0
pod 9 10 90.0
total 346 389 88.9


line stmt bran cond sub pod time code
1             package Test::Mock::Wrapper;
2             $Test::Mock::Wrapper::VERSION = '0.11';
3 4     4   215956 use strict;
  4         7  
  4         151  
4 3     3   12 use warnings;
  3         3  
  3         73  
5 3     3   12 use base qw(Exporter);
  3         7  
  3         234  
6 3     3   13 use Test::Deep;
  3         3  
  3         15  
7 3     3   605 use Test::More;
  3         4  
  3         15  
8 3     3   1452 use Clone qw(clone);
  3         5384  
  3         144  
9 3     3   16 use Scalar::Util qw(weaken isweak);
  3         4  
  3         131  
10 3     3   1107 use Module::Runtime qw(use_module);
  3         3452  
  3         24  
11             require Test::Mock::Wrapper::Verify;
12 3     3   140 use vars qw(%GLOBAL_MOCKS);
  3         4  
  3         112  
13 3     3   365 use lib qw(t/);
  3         472  
  3         15  
14              
15             sub import {
16 3     3   23 my($proto, @args) = @_;
17 3         48 foreach my $package (@args){
18 1         3 use_module $package;
19 1         406 $GLOBAL_MOCKS{$package} = Test::Mock::Wrapper->new($package);
20             }
21             }
22              
23             # ABSTRACT: Flexible and prowerful class and object mocking library for perl
24              
25             =head1 NAME
26              
27             Test::Mock::Wrapper
28              
29             =head1 VERSION
30              
31             version 0.11
32              
33             =head1 SYNOPSIS
34              
35             =head2 Mock a single instance of an object
36              
37             use Test::Mock::Wrapper;
38             use Foo;
39            
40             my $foo = Foo->new;
41             my $wrapper = Test::Mock::Wrapper->new($foo);
42            
43             $wrapper->addMock('bar')->with('baz')->returns('snarf');
44             # Old api, depricated but still supported
45             # $wrapper->addMock('bar', with=>['baz'], returns=>'snarf');
46             # #######################################
47            
48             &callBar($wrapper->getObject);
49            
50             $wrapper->verify('bar')->with(['baz'])->once;
51              
52             =head2 Mock an entire package
53              
54             use Test::Mock::Wrapper;
55             use Foo;
56            
57             my $wrapper = Test::Mock::Wrapper->new('Foo');
58            
59             $wrapper->addMock('bar')->with('baz')->returns('snarf');
60            
61             &callBar(Foo->new);
62            
63             $wrapper->verify('bar')->with(['baz'])->once;
64            
65             $wrapper->DESTROY;
66            
67             my $actualFoo = Foo->new;
68              
69             =head2 Mock Exported functions
70              
71             use Test::Mock::Wrapper qw(Foo);
72             use Foo qw(bar);
73            
74             is(&bar, undef); # Mocked version of bar, returns undef by default.
75            
76             my $wrapper = Test::Mock::Wrapper->new('Foo');
77            
78             $wrapper->addMock('bar')->with('baz')->returns('snarf');
79            
80             print &bar('baz'); # prints "snarf"
81            
82             $wrapper->verify('bar')->exactly(2); # $wrapper also saw the first &bar (even though it was before you instantiated it)
83            
84             $wrapper->DESTROY;
85            
86             print &bar('baz'); # Back to the original Foo::bar (whatever that did)
87              
88            
89             =head1 DESCRIPTION
90              
91             This is another module for mocking objects in perl. It will wrap around an existing object, allowing you to mock any calls
92             for testing purposes. It also records the arguments passed to the mocked methods for later examination. The verification
93             methods are designed to be chainable for easily readable tests for example:
94              
95             # Verify method foo was called with argument 'bar' at least once.
96             $mockWrapper->verify('foo')->with('bar')->at_least(1);
97            
98             # Verify method 'baz' was called at least 2 times, but not more than 5 times
99             $mockWrapper->verify('baz')->at_least(2)->at_most(5);
100              
101             Test::Mock::Wrapper can also be used to wrap an entire package. When this is done, Test::Mock::Wrapper will actually use
102             L<metaclass> to alter the symbol table an wrap all methods in the package. The same rules about mocking type (see options to
103             new below) apply to mocked packages, but you only get one wrapper that records and mocks calls to all instances of the package,
104             and any package methods called outside of an object. When mocking an entire package, destroying the wrapper object will "unwrap"
105             the package, restoring the symbol table to is original unmocked state. Objects instantiated before the wrapper was destroyed
106             may not behave correctly (i.e. throw exceptions).
107              
108             =head1 METHODS
109              
110             =over
111              
112             =item Test::Mock::Wrapper->new($object, [%options])
113              
114             Creates a new wrapped mock object and a controller/accessor object used to manipulate the mock without poluting the
115             namespace of the object being mocked.
116              
117             Valid options:
118              
119             =over 2
120              
121             =item B<type>=>(B<mock>|B<stub>|B<wrap>): Type of mocking to use.
122              
123             =over 3
124              
125             =item B<mock>: All methods available on the underlying object will be available, and all will be mocked
126              
127             =item B<stub>: Any method called on the mock object will be stubbed, even those which do not exist in the original
128             object
129              
130             =item B<wrap> (default): Only methods which have been specifically set up with B<addMock> will be mocked
131             all others will be passed through to the underlying object.
132              
133             =back
134              
135             =item recordAll=>BOOLEAN (default B<false>)
136              
137             If set to true, this will record the arguments to all calls made to the object, regardless of the method being
138             mocked or not.
139              
140             =item recordMethod=>(B<copy>|B<clone>)
141              
142             By default arguments will be a simple copy of @_, use B<clone> to make a deep copy of all data passed in. If references are being
143             passed in, the default will not trap the state of the object or reference at the time the method was called, though clone will.
144             Naturally using clone will cause a larger memory foot print.
145              
146             =back
147              
148             =cut
149              
150             sub new {
151 32     32 1 16724 my($proto, $object, %options) = @_;
152 32 100 66     123 $options{type} ||= ref($object) ? 'wrap' : 'stub';
153 32   50     95 $options{recordType} ||= 'copy';
154 32   33     82 my $class = ref($proto) || $proto;
155 32         106 my $controll = bless({__object=>$object, __mocks=>{}, __calls=>{}, __options=>\%options}, $class);
156 32         81 $controll->{__mocked} = Test::Mock::Wrapped->new($controll, $object);
157 32 100       51 if (! ref($object)) {
158 5 100       16 if (exists $GLOBAL_MOCKS{$object}) {
159 2         7 return $GLOBAL_MOCKS{$object};
160             }
161            
162 3     2   160 eval "package $object; use metaclass;";
  2         356  
  2         85384  
  2         11  
163 3         1333 my $metaclass = $object->meta;
164              
165 3 100       35 $metaclass->make_mutable if($metaclass->is_immutable);
166              
167 3         103 $controll->{__metaclass} = $metaclass;
168            
169 3         23 foreach my $method_name ($metaclass->get_method_list){
170 8         545 push @{ $controll->{__wrapped_symbols} }, {name => $method_name, symbol => $metaclass->find_method_by_name($method_name)};
  8         22  
171 8         186 $controll->{__symbols}{$method_name} = $metaclass->find_method_by_name($method_name)->body;
172 8 100       161 if ($method_name eq 'new') {
173 1         2 my $method = $metaclass->remove_method($method_name);
174             $metaclass->add_method($method_name, sub{
175 1 50   1   6 my $copy = $controll->{__options}{recordType} eq 'copy' ? [@_] : clone(@_);
176 1         1 push @{ $controll->{__calls}{new} }, $copy;
  1         2  
177 1         2 my $obj = bless {}, $object;
178 1         1 push @{ $controll->{__instances} }, $obj;
  1         2  
179 1         2 return $obj;
180 1         20 });
181            
182             }else{
183 7         19 my $method = $metaclass->remove_method($method_name);
184 7     4   154 $metaclass->add_method($method_name, sub{ $controll->_call($method_name, @_); });
  4         1529  
185             }
186             }
187             }
188 30         159 return $controll;
189             }
190              
191             sub stop_mocking {
192 26     31 0 16 my $controll = shift;
193 3     3   1137 no strict qw(refs);
  3         4  
  3         84  
194 3     3   9 no warnings 'redefine', 'prototype';
  3         2  
  3         1561  
195 26         41 $controll->resetAll;
196 26 100       101 if ($controll->{__metaclass}) {
197 3         3 foreach my $sym (@{ $controll->{__wrapped_symbols} }){
  3         5  
198 10 50       320 if ($sym->{symbol}) {
199 10         37 $controll->{__metaclass}->add_method($sym->{name}, $sym->{symbol}->body);
200             }
201             }
202             }
203 26         192 $controll->{__options}{type} = 'wrap';
204             }
205              
206             sub DESTROY {
207 26     29   1551 shift->stop_mocking;
208             }
209              
210             =item $wrapper->getObject
211              
212             This method returns the wrapped 'mock' object. The object is actually a Test::Mock::Wrapped object, however it can be used
213             exactly as the object originally passed to the constructor would be, with the additional hooks provieded by the wrapper
214             baked in.
215              
216             =cut
217              
218             sub getObject {
219 51     51 1 1382 my $self = shift;
220 51         215 return $self->{__mocked};
221             }
222              
223             sub _call {
224 50     50   38 my $self = shift;
225 50         35 my $method = shift;
226 50 50       99 my $copy = $self->{__options}{recordType} eq 'copy' ? [@_] : clone(@_);
227 50         37 push @{ $self->{__calls}{$method} }, $copy;
  50         78  
228            
229 50 100       88 if ($self->{__mocks}{$method}) {
230 40         55 my $mock = $self->{__mocks}{$method}->hasMock(@_);
231 40 50       56 if ($mock) {
232 40         54 return $mock->_fetchReturn(@_);
233             }
234            
235             }
236            
237 10 100       17 if($self->{__options}{type} ne 'wrap'){
238             # No default, type equals stub or mock, return undef.
239 9         26 return undef;
240             }
241             else{
242             # We do not have a default, and our mock type is not stub or mock, try to call underlying object.
243 1         3 unshift @_, $self->{__object};
244 1 50       3 if ($self->{__metaclass}) {
245             # Pacakge is mocked with method wrappers, must call the original symbol metaclass
246 1         1 goto &{ $self->{__symbols}{$method} };
  1         6  
247             }else{
248 0         0 goto &{ ref($self->{__object}).'::'.$method };
  0         0  
249             }
250            
251             }
252             }
253              
254             =item $wrapper->addMock($method, [OPTIONS])
255              
256             This method is used to add a new mocked method call. Currently supports two optional parameters:
257              
258             =over 2
259              
260             =item * B<returns> used to specify a value to be returned when the method is called.
261              
262             $wrapper->addMock('foo', returns=>'bar')
263            
264             Note: if "returns" recieves an array refernce, it will return it as an array. To return an actual
265             array reference, wrap it in another reference.
266              
267             $wrapper->addMock('foo', returns=>['Dave', 'Fred', 'Harry'])
268             my(@names) = $wrapper->getObject->foo;
269            
270             $wrapper->addMock('baz', returns=>[['Dave', 'Fred', 'Harry']]);
271             my($rnames) = $wrapper->getObject->baz;
272              
273             =item * B<with> used to limit the scope of the mock based on the value of the arguments. Test::Deep's eq_deeply is used to
274             match against the provided arguments, so any syntax supported there will work with Test::Mock::Wrapper;
275              
276             $wrapper->addMock('foo', with=>['baz'], returns=>'bat')
277              
278             =back
279              
280             The B<with> option is really only usefull to specify a different return value based on the arguments passed to the mocked method.
281             When addMock is called with no B<with> option, the B<returns> value is used as the "default", meaning it will be returned only
282             if the arguments passed to the mocked method do not match any of the provided with conditions.
283              
284             For example:
285              
286             $wrapper->addMock('foo', returns=>'bar');
287             $wrapper->addMock('foo', with=>['baz'], returns=>'bat');
288             $wrapper->addMock('foo', with=>['bam'], returns=>'ouch');
289            
290             my $mocked = $wrapper->getObject;
291            
292             print $mocked->foo('baz'); # prints 'bat'
293             print $mocked->foo('flee'); # prints 'bar'
294             print $mocked->foo; # prints 'bar'
295             print $mocked->foo('bam'); # prints 'ouch'
296            
297              
298             =cut
299              
300             sub addMock {
301 29     29 1 1747 my $self = shift;
302 29         39 my($method, %options) = @_;
303 29   66     97 $self->{__mocks}{$method} ||= Test::Mock::Wrapper::Method->new();
304 29         71 return $self->{__mocks}{$method}->addMock(%options);
305             }
306              
307              
308             =item $wrapper->isMocked($method, $args)
309              
310             This is a boolean method which returns true if a call to the specified method on the underlying wrapped object would be handled by a mock,
311             and false otherwise. Any conditional mocks specified with the B<with> option will be evaluated accordingly.
312              
313             $wrapper->addMock('foo', with=>['bar'], returns=>'baz');
314             $wrapper->isMocked('foo', ['bam']); # False
315             $wrapper->isMocked('foo', ['bar']); # True
316              
317             =cut
318              
319             sub isMocked {
320 51     51 1 32 my $self = shift;
321 51         40 my $method = shift;
322 51         45 my(@args) = @_;
323 51 100       120 if ($self->{__options}{type} eq 'stub') {
    100          
324 2         3 return 1;
325             }
326             elsif ($self->{__options}{type} eq 'mock') {
327 36         95 return $self->{__object}->can($method);
328             }
329             else {
330 13 100 100     45 if ($self->{__mocks}{$method} && $self->{__mocks}{$method}->hasMock(@args)) {
331 9         19 return 1;
332             } else {
333 4         10 return undef;
334             }
335             }
336             }
337              
338             =item $wrapper->getCallsTo($method)
339              
340             This method wil return an array of the arguments passed to each call to the specified method, in the order they were recieved.
341              
342             =cut
343              
344             sub getCallsTo {
345 0     0 1 0 my $self = shift;
346 0         0 my $method = shift;
347 0 0       0 if (exists $self->{__calls}{$method}) {
348 0   0     0 return $self->{__calls}{$method} || [];
349             }
350 0         0 return;
351             }
352              
353             =item $wrapper->verify($method)
354              
355             This call returns a Test::Mock::Wrapper::Verify object, which can be used to examine any calls which have been made to the
356             specified method thus far. These objects are intended to be used to simplify testing, and methods called on the it
357             are I<chainable> to lend to more readable tests.
358              
359             =cut
360              
361             sub verify {
362 19     19 1 35 my($self, $method, %options) = @_;
363 19         56 return Test::Mock::Wrapper::Verify->new($method, $self->{__calls}{$method});
364             }
365              
366              
367             =item $wrapper->resetCalls([$method])
368              
369             This method clears out the memory of calls that have been made, which is usefull if using the same mock wrapper instance
370             multiple tests. When called without arguments, all call history is cleared. With the optional $method argument, only
371             history for that method is called.
372              
373             =cut
374              
375             sub resetCalls {
376 2     2 1 235 my($self, $method) = @_;
377 2 100 66     7 if (defined($method) && length($method)) {
378 1         3 $self->{__calls}{$method} = [];
379             }else{
380 1         2 $self->{__calls} = {};
381             }
382 2         3 return 1;
383             }
384              
385             =item $wrapper->resetMocks([$method])
386              
387             This method clears out all previously provided mocked methods. Without arguments, all mocks are cleared. With the optional
388             $method argument, only mocks for that method are cleared.
389              
390             =cut
391              
392             sub resetMocks {
393 2     2 1 7 my($self, $method) = @_;
394 2 100 66     9 if (defined($method) && length($method)) {
395 1         6 delete $self->{__mocks}{$method};
396             }else{
397 1         2 $self->{__mocks} = {};
398             }
399 2         9 return 1;
400             }
401              
402             =item $wrapper->resetAll
403              
404             This method clears out both mocks and calls. Will also rebless any mocked instances created from a mocked package
405             (Prevents intermitent failures during global destruction).
406              
407             =back
408              
409             =cut
410              
411             sub resetAll {
412 26     26 1 18 my $self = shift;
413 26 100       67 if ($self->{__metaclass}) {
414 3         4 foreach my $inst (@{ $self->{__instances} }){
  3         5  
415 1 50       6 bless $inst, 'Test::Mock::Wrapped' if($inst);
416             }
417             }
418 26         37 $self->{__instances} = [];
419 26         29 $self->{__calls} = {};
420 26         50 $self->{__mocks} = {};
421             }
422              
423              
424             package Test::Mock::Wrapper::Method;
425             $Test::Mock::Wrapper::Method::VERSION = '0.11';
426 3     3   14 use Test::Deep;
  3         2  
  3         17  
427 3     3   605 use strict;
  3         3  
  3         66  
428 3     3   11 use warnings;
  3         2  
  3         469  
429              
430             sub new {
431 26     26   25 my($proto, %args) = @_;
432 26   33     59 $proto = ref($proto) || $proto;
433 26         84 return bless({_mocks=>[]}, $proto)
434             }
435              
436             sub addMock {
437 29     29   31 my($self, %args) = @_;
438 29         56 my $mock = Test::Mock::Wrapper::Method::Mock->new();
439 29 100       46 $mock->with(@{$args{with}}) if(exists $args{with});
  4         7  
440 29 100       44 $mock->returns($args{returns}) if(exists $args{returns});
441 29         17 push @{ $self->{_mocks} }, $mock;
  29         42  
442 29         46 return $mock;
443             }
444              
445             sub hasMock {
446 51     51   165 my($self, @args) = @_;
447 51         40 foreach my $mock (@{$self->{_mocks}}){
  51         70  
448 53 100       1125 if ($mock->_matches(@args)) {
449 49         12605 return $mock;
450             }
451             }
452 2         7309 return undef;
453             }
454              
455             package Test::Mock::Wrapper::Method::Mock;
456             $Test::Mock::Wrapper::Method::Mock::VERSION = '0.11';
457 3     3   10 use Test::Deep;
  3         3  
  3         9  
458 3     3   514 use strict;
  3         3  
  3         56  
459 3     3   9 use warnings;
  3         3  
  3         652  
460              
461             sub new {
462 29     29   23 my($proto, %args) = @_;
463 29   33     77 $proto = ref($proto) || $proto;
464 29         27 my $self = {};
465 29 50       41 if ($args{with}) {
466 0         0 $self->{_condition} = $args{with};
467             }
468 29 50       39 if ($args{returns}) {
469 0         0 $self->{_return} = $args{returns};
470             }
471            
472 29         57 return bless($self, $proto)
473             }
474              
475             sub with {
476 8     8   12 my($self, @args) = @_;
477 8         12 $self->{_condition} = \@args;
478 8         20 return $self;
479             }
480              
481             sub returns {
482 14     14   16 my($self, @value) = @_;
483 14 50       34 $self->{_return} = scalar(@value) > 1 ? \@value : $value[0];
484             }
485              
486             sub _isDefault {
487 0     0   0 my($self) = @_;
488 0         0 return exists($self->{_condition});
489             }
490              
491             sub _matches {
492 53     53   33 my $self = shift;
493 53         47 my(@args) = @_;
494 53 100       68 if (exists $self->{_condition}) {
495 16         39 return eq_deeply(\@args, $self->{_condition});
496             }else{
497 37         57 return 1;
498             }
499             }
500              
501             sub _fetchReturn {
502 40     40   29 my($self, @args) = @_;
503 40 50       77 if (ref($self->{_return}) eq 'ARRAY') {
    100          
504 0         0 return @{ $self->{_return} };
  0         0  
505             }elsif(ref($self->{_return}) eq 'CODE'){
506 2         3 return $self->{_return}->(@args);
507             }else{
508 38         104 return $self->{_return};
509             }
510             }
511              
512              
513             package Test::Mock::Wrapped;
514             $Test::Mock::Wrapped::VERSION = '0.11';
515 3     3   10 use strict;
  3         3  
  3         56  
516 3     3   9 use warnings;
  3         1  
  3         48  
517 3     3   6 use Carp;
  3         3  
  3         140  
518 3     3   10 use Scalar::Util qw(weaken isweak);
  3         2  
  3         97  
519 3     3   9 use vars qw(@ISA);
  3         2  
  3         620  
520              
521             sub new {
522 32     32   34 my($proto, $controller, $object) = @_;
523 32         58 weaken($controller);
524 32   33     68 my $class = ref($proto) || $proto;
525 32         66 my $self = bless({__controller=>$controller, __object=>$object}, $class);
526 32         67 weaken($self->{__controller});
527 32         60 return $self;
528             }
529              
530             sub AUTOLOAD {
531 51     51   36 my $self = shift;
532 51         49 my(@args) = @_;
533 51         194 $Test::Mock::Wrapped::AUTOLOAD=~m/::(\w+)$/;
534 51         68 my $method = $1;
535 51 100       80 if ($self->{__controller}->isMocked($method, @args)) {
536 46         70 return $self->{__controller}->_call($method, @args);
537             }
538             else {
539 5 100       24 if ($self->{__object}->can($method)) {
540 3         5 unshift @_, $self->{__object};
541 3         4 goto &{ ref($self->{__object}).'::'.$method };
  3         16  
542             }
543             else {
544 2         4 my $pack = ref($self->{__object});
545 2         217 croak qq{Can't locate object method "$method" via package "$pack"};
546             }
547             }
548             }
549              
550             return 42;
551              
552             =head1 AUTHOR
553              
554             Dave Mueller <dave@perljedi.com>
555              
556             =head1 COPYRIGHT AND LICENSE
557              
558             This software is copyright (c) 2015 by Dave Mueller.
559              
560             This is free software; you can redistribute it and/or modify it under the
561             same terms as the Perl 5 programming language system itself.