File Coverage

blib/lib/Test/Mock/Wrapper.pm
Criterion Covered Total %
statement 227 238 95.3
branch 54 66 81.8
condition 16 31 51.6
subroutine 49 50 98.0
pod 9 10 90.0
total 355 395 89.8


line stmt bran cond sub pod time code
1             package Test::Mock::Wrapper;
2             $Test::Mock::Wrapper::VERSION = '0.12';
3 4     4   207321 use strict;
  4         5  
  4         76  
4 4     4   12 use warnings;
  4         4  
  4         53  
5 3     3   9 use base qw(Exporter);
  3         4  
  3         153  
6 3     3   11 use Test::Deep;
  3         3  
  3         11  
7 3     3   477 use Test::More;
  3         3  
  3         17  
8 3     3   1338 use Clone qw(clone);
  3         6629  
  3         170  
9 3     3   17 use Scalar::Util qw(weaken isweak);
  3         4  
  3         110  
10 3     3   1296 use Module::Runtime qw(use_module);
  3         3334  
  3         14  
11             require Test::Mock::Wrapper::Verify;
12 3     3   120 use vars qw(%GLOBAL_MOCKS);
  3         4  
  3         91  
13 3     3   415 use lib qw(t/);
  3         486  
  3         15  
14              
15             sub import {
16 3     3   19 my($proto, @args) = @_;
17 3         41 foreach my $package (@args){
18 1         2 use_module $package;
19 1         360 $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.12
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 35     35 1 15385 my($proto, $object, %options) = @_;
152 35 100 66     135 $options{type} ||= ref($object) ? 'wrap' : 'stub';
153 35   50     112 $options{recordType} ||= 'copy';
154 35   33     99 my $class = ref($proto) || $proto;
155 35         123 my $controll = bless({__object=>$object, __mocks=>{}, __calls=>{}, __options=>\%options}, $class);
156 35         91 $controll->{__mocked} = Test::Mock::Wrapped->new($controll, $object);
157 35 100       75 if (! ref($object)) {
158 6 100       15 if (exists $GLOBAL_MOCKS{$object}) {
159 2         5 return $GLOBAL_MOCKS{$object};
160             }
161            
162 4     2   221 eval "package $object; use metaclass;";
  2         336  
  2         86510  
  2         11  
163 4         1900 my $metaclass = $object->meta;
164              
165 4 100       45 $metaclass->make_mutable if($metaclass->is_immutable);
166              
167 4         112 $controll->{__metaclass} = $metaclass;
168            
169 4         27 foreach my $method_name ($metaclass->get_method_list){
170 12         812 push @{ $controll->{__wrapped_symbols} }, {name => $method_name, symbol => $metaclass->find_method_by_name($method_name)};
  12         32  
171 12         302 $controll->{__symbols}{$method_name} = $metaclass->find_method_by_name($method_name)->body;
172 12 100       249 if ($method_name eq 'new') {
173 2         3 my $method = $metaclass->remove_method($method_name);
174             $metaclass->add_method($method_name, sub{
175 15 50   15   3882 my $copy = $controll->{__options}{recordType} eq 'copy' ? [@_] : clone(@_);
176 15         17 push @{ $controll->{__calls}{new} }, $copy;
  15         34  
177 15         14 my $obj = bless {_inst => scalar(@{ $controll->{__calls}{new} })}, $object;
  15         45  
178 15         12 push @{ $controll->{__instances} }, $obj;
  15         20  
179 15         38 return $obj;
180 2         49 });
181            
182             }else{
183 10         32 my $method = $metaclass->remove_method($method_name);
184 10     21   253 $metaclass->add_method($method_name, sub{ $controll->_call($method_name, @_); });
  6         1571  
185             }
186             }
187             }
188 33         199 return $controll;
189             }
190              
191             sub stop_mocking {
192 28     37 0 30 my $controll = shift;
193 3     3   1153 no strict qw(refs);
  3         4  
  3         80  
194 3     3   10 no warnings 'redefine', 'prototype';
  3         4  
  3         1631  
195 28         46 $controll->resetAll;
196 28 100       131 if ($controll->{__metaclass}) {
197 3         3 foreach my $sym (@{ $controll->{__wrapped_symbols} }){
  3         6  
198 10 50       369 if ($sym->{symbol}) {
199 10         40 $controll->{__metaclass}->add_method($sym->{name}, $sym->{symbol}->body);
200             }
201             }
202             }
203 28         213 $controll->{__options}{type} = 'wrap';
204             }
205              
206             sub DESTROY {
207 28     37   1902 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 55     58 1 2125 my $self = shift;
220 55         254 return $self->{__mocked};
221             }
222              
223             sub _call {
224 56     56   42 my $self = shift;
225 56         44 my $method = shift;
226 56 50       117 my $copy = $self->{__options}{recordType} eq 'copy' ? [@_] : clone(@_);
227 56         43 push @{ $self->{__calls}{$method} }, $copy;
  56         93  
228            
229 56 100       99 if ($self->{__mocks}{$method}) {
230 46         75 my $mock = $self->{__mocks}{$method}->hasMock(@_);
231 46 50       75 if ($mock) {
232 46         67 return $mock->_fetchReturn(@_);
233             }
234            
235             }
236            
237 10 100       19 if($self->{__options}{type} ne 'wrap'){
238             # No default, type equals stub or mock, return undef.
239 9         30 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         2 unshift @_, $self->{__object};
244 1 50       2 if ($self->{__metaclass}) {
245             # Pacakge is mocked with method wrappers, must call the original symbol metaclass
246 1         2 goto &{ $self->{__symbols}{$method} };
  1         5  
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 35     35 1 2018 my $self = shift;
302 35         59 my($method, %options) = @_;
303 35   66     139 $self->{__mocks}{$method} ||= Test::Mock::Wrapper::Method->new();
304 35         83 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 55     55 1 44 my $self = shift;
321 55         43 my $method = shift;
322 55         53 my(@args) = @_;
323 55 100       133 if ($self->{__options}{type} eq 'stub') {
    100          
324 2         4 return 1;
325             }
326             elsif ($self->{__options}{type} eq 'mock') {
327 36         110 return $self->{__object}->can($method);
328             }
329             else {
330 17 100 100     52 if ($self->{__mocks}{$method} && $self->{__mocks}{$method}->hasMock(@args)) {
331 13         28 return 1;
332             } else {
333 4         33 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 37 my($self, $method, %options) = @_;
363 19         67 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 269 my($self, $method) = @_;
377 2 100 66     9 if (defined($method) && length($method)) {
378 1         2 $self->{__calls}{$method} = [];
379             }else{
380 1         2 $self->{__calls} = {};
381             }
382 2         5 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 4 my($self, $method) = @_;
394 2 100 66     10 if (defined($method) && length($method)) {
395 1         7 delete $self->{__mocks}{$method};
396             }else{
397 1         2 $self->{__mocks} = {};
398             }
399 2         8 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 28     28 1 24 my $self = shift;
413 28 100       74 if ($self->{__metaclass}) {
414 3         4 foreach my $inst (@{ $self->{__instances} }){
  3         6  
415 1 50       6 bless $inst, 'Test::Mock::Wrapped' if($inst);
416             }
417             }
418 28         41 $self->{__instances} = [];
419 28         43 $self->{__calls} = {};
420 28         59 $self->{__mocks} = {};
421             }
422              
423              
424             package Test::Mock::Wrapper::Method;
425             $Test::Mock::Wrapper::Method::VERSION = '0.12';
426 3     3   12 use Test::Deep;
  3         3  
  3         15  
427 3     3   641 use strict;
  3         3  
  3         204  
428 3     3   15 use warnings;
  3         3  
  3         565  
429              
430             sub new {
431 29     29   32 my($proto, %args) = @_;
432 29   33     80 $proto = ref($proto) || $proto;
433 29         105 return bless({_mocks=>[]}, $proto)
434             }
435              
436             sub addMock {
437 35     35   37 my($self, %args) = @_;
438 35         82 my $mock = Test::Mock::Wrapper::Method::Mock->new();
439 35 100       55 $mock->with(@{$args{with}}) if(exists $args{with});
  6         15  
440 35 100       67 $mock->returns($args{returns}) if(exists $args{returns});
441 35         34 push @{ $self->{_mocks} }, $mock;
  35         45  
442 35         83 return $mock;
443             }
444              
445             sub hasMock {
446 61     61   68 my($self, @args) = @_;
447 61         60 my $def = undef;
448 61         38 foreach my $mock (@{$self->{_mocks}}){
  61         82  
449 70 100       7516 if ($mock->_matches(@args)) {
450 61 100       17589 if ($mock->_isDefault) {
451 43         61 $def = $mock;
452             }else{
453 18         46 return $mock;
454             }
455             }
456             }
457 43         6887 return $def;
458             }
459              
460             package Test::Mock::Wrapper::Method::Mock;
461             $Test::Mock::Wrapper::Method::Mock::VERSION = '0.12';
462 3     3   12 use Test::Deep;
  3         2  
  3         8  
463 3     3   545 use strict;
  3         3  
  3         60  
464 3     3   9 use warnings;
  3         2  
  3         693  
465              
466             sub new {
467 35     35   32 my($proto, %args) = @_;
468 35   33     79 $proto = ref($proto) || $proto;
469 35         32 my $self = {};
470 35 50       57 if ($args{with}) {
471 0         0 $self->{_condition} = $args{with};
472             }
473 35 50       55 if ($args{returns}) {
474 0         0 $self->{_return} = $args{returns};
475             }
476            
477 35         67 return bless($self, $proto)
478             }
479              
480             sub with {
481 12     12   18 my($self, @args) = @_;
482 12         18 $self->{_condition} = \@args;
483 12         26 return $self;
484             }
485              
486             sub returns {
487 20     20   20 my($self, @value) = @_;
488 20 50       42 $self->{_return} = scalar(@value) > 1 ? \@value : $value[0];
489             }
490              
491             sub _isDefault {
492 61     61   50 my($self) = @_;
493 61         153 return ! exists($self->{_condition});
494             }
495              
496             sub _matches {
497 70     70   60 my $self = shift;
498 70         73 my(@args) = @_;
499 70 100       92 if (exists $self->{_condition}) {
500 27         64 return eq_deeply(\@args, $self->{_condition});
501             }else{
502 43         85 return 1;
503             }
504             }
505              
506             sub _fetchReturn {
507 46     46   46 my($self, @args) = @_;
508 46 50       97 if (ref($self->{_return}) eq 'ARRAY') {
    100          
509 0         0 return @{ $self->{_return} };
  0         0  
510             }elsif(ref($self->{_return}) eq 'CODE'){
511 4         9 return $self->{_return}->(@args);
512             }else{
513 42         167 return $self->{_return};
514             }
515             }
516              
517              
518             package Test::Mock::Wrapped;
519             $Test::Mock::Wrapped::VERSION = '0.12';
520 3     3   11 use strict;
  3         3  
  3         56  
521 3     3   9 use warnings;
  3         3  
  3         46  
522 3     3   8 use Carp;
  3         3  
  3         133  
523 3     3   10 use Scalar::Util qw(weaken isweak);
  3         2  
  3         98  
524 3     3   9 use vars qw(@ISA);
  3         2  
  3         621  
525              
526             sub new {
527 35     35   34 my($proto, $controller, $object) = @_;
528 35         78 weaken($controller);
529 35   33     81 my $class = ref($proto) || $proto;
530 35         101 my $self = bless({__controller=>$controller, __object=>$object}, $class);
531 35         76 weaken($self->{__controller});
532 35         72 return $self;
533             }
534              
535             sub AUTOLOAD {
536 55     55   48 my $self = shift;
537 55         58 my(@args) = @_;
538 55         238 $Test::Mock::Wrapped::AUTOLOAD=~m/::(\w+)$/;
539 55         76 my $method = $1;
540 55 100       99 if ($self->{__controller}->isMocked($method, @args)) {
541 50         83 return $self->{__controller}->_call($method, @args);
542             }
543             else {
544 5 100       34 if ($self->{__object}->can($method)) {
545 3         8 unshift @_, $self->{__object};
546 3         4 goto &{ ref($self->{__object}).'::'.$method };
  3         21  
547             }
548             else {
549 2         3 my $pack = ref($self->{__object});
550 2         204 croak qq{Can't locate object method "$method" via package "$pack"};
551             }
552             }
553             }
554              
555             return 42;
556              
557             =head1 AUTHOR
558              
559             Dave Mueller <dave@perljedi.com>
560              
561             =head1 COPYRIGHT AND LICENSE
562              
563             This software is copyright (c) 2015 by Dave Mueller.
564              
565             This is free software; you can redistribute it and/or modify it under the
566             same terms as the Perl 5 programming language system itself.