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.10';
3 4     4   226505 use strict;
  4         7  
  4         122  
4 3     3   10 use warnings;
  3         3  
  3         59  
5 3     3   9 use base qw(Exporter);
  3         4  
  3         185  
6 3     3   10 use Test::Deep;
  3         3  
  3         16  
7 3     3   683 use Test::More;
  3         6  
  3         15  
8 3     3   1796 use Clone qw(clone);
  3         5520  
  3         159  
9 3     3   13 use Scalar::Util qw(weaken isweak);
  3         3  
  3         115  
10 3     3   1339 use Module::Runtime qw(use_module);
  3         3538  
  3         14  
11             require Test::Mock::Wrapper::Verify;
12 3     3   126 use vars qw(%GLOBAL_MOCKS);
  3         9  
  3         100  
13 3     3   416 use lib qw(t/);
  3         414  
  3         12  
14              
15             sub import {
16 3     3   23 my($proto, @args) = @_;
17 3         55 foreach my $package (@args){
18 1         4 use_module $package;
19 1         551 $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.10
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 18288 my($proto, $object, %options) = @_;
152 32 100 66     141 $options{type} ||= ref($object) ? 'wrap' : 'stub';
153 32   50     97 $options{recordType} ||= 'copy';
154 32   33     85 my $class = ref($proto) || $proto;
155 32         114 my $controll = bless({__object=>$object, __mocks=>{}, __calls=>{}, __options=>\%options}, $class);
156 32         78 $controll->{__mocked} = Test::Mock::Wrapped->new($controll, $object);
157 32 100       55 if (! ref($object)) {
158 5 100       18 if (exists $GLOBAL_MOCKS{$object}) {
159 2         7 return $GLOBAL_MOCKS{$object};
160             }
161            
162 3     2   161 eval "package $object; use metaclass;";
  2         348  
  2         94596  
  2         14  
163 3         1504 my $metaclass = $object->meta;
164              
165 3 100       34 $metaclass->make_mutable if($metaclass->is_immutable);
166              
167 3         109 $controll->{__metaclass} = $metaclass;
168            
169 3         23 foreach my $method_name ($metaclass->get_method_list){
170 8         553 push @{ $controll->{__wrapped_symbols} }, {name => $method_name, symbol => $metaclass->find_method_by_name($method_name)};
  8         20  
171 8         189 $controll->{__symbols}{$method_name} = $metaclass->find_method_by_name($method_name)->body;
172 8 100       260 if ($method_name eq 'new') {
173 1         7 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         3  
177 1         1 my $obj = bless {}, $object;
178 1         2 push @{ $controll->{__instances} }, $obj;
  1         1  
179 1         2 return $obj;
180 1         29 });
181            
182             }else{
183 7         18 my $method = $metaclass->remove_method($method_name);
184 7     4   155 $metaclass->add_method($method_name, sub{ $controll->_call($method_name, @_); });
  4         1753  
185             }
186             }
187             }
188 30         154 return $controll;
189             }
190              
191             sub stop_mocking {
192 26     31 0 26 my $controll = shift;
193 3     3   1225 no strict qw(refs);
  3         3  
  3         74  
194 3     3   9 no warnings 'redefine', 'prototype';
  3         4  
  3         1720  
195 26         42 $controll->resetAll;
196 26 100       107 if ($controll->{__metaclass}) {
197 3         4 foreach my $sym (@{ $controll->{__wrapped_symbols} }){
  3         5  
198 10 50       347 if ($sym->{symbol}) {
199 10         37 $controll->{__metaclass}->add_method($sym->{name}, $sym->{symbol}->body);
200             }
201             }
202             }
203 26         202 $controll->{__options}{type} = 'wrap';
204             }
205              
206             sub DESTROY {
207 26     29   1415 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 1551 my $self = shift;
220 51         267 return $self->{__mocked};
221             }
222              
223             sub _call {
224 50     50   37 my $self = shift;
225 50         40 my $method = shift;
226 50 50       100 my $copy = $self->{__options}{recordType} eq 'copy' ? [@_] : clone(@_);
227 50         32 push @{ $self->{__calls}{$method} }, $copy;
  50         76  
228            
229 50 100       94 if ($self->{__mocks}{$method}) {
230 40         62 my $mock = $self->{__mocks}{$method}->hasMock(@_);
231 40 50       56 if ($mock) {
232 40         78 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         28 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       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         4  
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 1822 my $self = shift;
302 29         47 my($method, %options) = @_;
303 29   66     99 $self->{__mocks}{$method} ||= Test::Mock::Wrapper::Method->new();
304 29         67 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 47 my $self = shift;
321 51         33 my $method = shift;
322 51         35 my(@args) = @_;
323 51 100       129 if ($self->{__options}{type} eq 'stub') {
    100          
324 2         3 return 1;
325             }
326             elsif ($self->{__options}{type} eq 'mock') {
327 36         100 return $self->{__object}->can($method);
328             }
329             else {
330 13 100 100     42 if ($self->{__mocks}{$method} && $self->{__mocks}{$method}->hasMock(@args)) {
331 9         21 return 1;
332             } else {
333 4         9 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->{__mocks}{$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         58 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 245 my($self, $method) = @_;
377 2 100 66     8 if (defined($method) && length($method)) {
378 1         3 $self->{__calls}{$method} = [];
379             }else{
380 1         2 $self->{__calls} = {};
381             }
382 2         4 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 26     26 1 23 my $self = shift;
413 26 100       101 if ($self->{__metaclass}) {
414 3         2 foreach my $inst (@{ $self->{__instances} }){
  3         6  
415 1 50       7 bless $inst, 'Test::Mock::Wrapped' if($inst);
416             }
417             }
418 26         37 $self->{__instances} = [];
419 26         29 $self->{__calls} = {};
420 26         52 $self->{__mocks} = {};
421             }
422              
423              
424             package Test::Mock::Wrapper::Method;
425             $Test::Mock::Wrapper::Method::VERSION = '0.10';
426 3     3   14 use Test::Deep;
  3         2  
  3         19  
427 3     3   612 use strict;
  3         3  
  3         57  
428 3     3   10 use warnings;
  3         3  
  3         521  
429              
430             sub new {
431 26     26   24 my($proto, %args) = @_;
432 26   33     58 $proto = ref($proto) || $proto;
433 26         88 return bless({_mocks=>[]}, $proto)
434             }
435              
436             sub addMock {
437 29     29   31 my($self, %args) = @_;
438 29         60 my $mock = Test::Mock::Wrapper::Method::Mock->new();
439 29 100       47 $mock->with(@{$args{with}}) if(exists $args{with});
  4         8  
440 29 100       49 $mock->returns($args{returns}) if(exists $args{returns});
441 29         22 push @{ $self->{_mocks} }, $mock;
  29         39  
442 29         51 return $mock;
443             }
444              
445             sub hasMock {
446 51     51   49 my($self, @args) = @_;
447 51         30 foreach my $mock (@{$self->{_mocks}}){
  51         72  
448 53 100       1254 if ($mock->_matches(@args)) {
449 49         13495 return $mock;
450             }
451             }
452 2         5558 return undef;
453             }
454              
455             package Test::Mock::Wrapper::Method::Mock;
456             $Test::Mock::Wrapper::Method::Mock::VERSION = '0.10';
457 3     3   10 use Test::Deep;
  3         3  
  3         9  
458 3     3   522 use strict;
  3         4  
  3         59  
459 3     3   9 use warnings;
  3         3  
  3         764  
460              
461             sub new {
462 29     29   26 my($proto, %args) = @_;
463 29   33     73 $proto = ref($proto) || $proto;
464 29         26 my $self = {};
465 29 50       39 if ($args{with}) {
466 0         0 $self->{_condition} = $args{with};
467             }
468 29 50       38 if ($args{returns}) {
469 0         0 $self->{_return} = $args{returns};
470             }
471            
472 29         54 return bless($self, $proto)
473             }
474              
475             sub with {
476 8     8   11 my($self, @args) = @_;
477 8         14 $self->{_condition} = \@args;
478 8         21 return $self;
479             }
480              
481             sub returns {
482 14     14   14 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   34 my $self = shift;
493 53         49 my(@args) = @_;
494 53 100       72 if (exists $self->{_condition}) {
495 16         42 return eq_deeply(\@args, $self->{_condition});
496             }else{
497 37         62 return 1;
498             }
499             }
500              
501             sub _fetchReturn {
502 40     40   39 my($self, @args) = @_;
503 40 50       84 if (ref($self->{_return}) eq 'ARRAY') {
    100          
504 0         0 return @{ $self->{_return} };
  0         0  
505             }elsif(ref($self->{_return}) eq 'CODE'){
506 2         4 return $self->{_return}->(@args);
507             }else{
508 38         84 return $self->{_return};
509             }
510             }
511              
512              
513             package Test::Mock::Wrapped;
514             $Test::Mock::Wrapped::VERSION = '0.10';
515 3     3   12 use strict;
  3         2  
  3         61  
516 3     3   9 use warnings;
  3         3  
  3         57  
517 3     3   15 use Carp;
  3         3  
  3         149  
518 3     3   12 use Scalar::Util qw(weaken isweak);
  3         4  
  3         130  
519 3     3   12 use vars qw(@ISA);
  3         2  
  3         675  
520              
521             sub new {
522 32     32   38 my($proto, $controller, $object) = @_;
523 32         61 weaken($controller);
524 32   33     73 my $class = ref($proto) || $proto;
525 32         76 my $self = bless({__controller=>$controller, __object=>$object}, $class);
526 32         64 weaken($self->{__controller});
527 32         64 return $self;
528             }
529              
530             sub AUTOLOAD {
531 51     51   41 my $self = shift;
532 51         47 my(@args) = @_;
533 51         196 $Test::Mock::Wrapped::AUTOLOAD=~m/::(\w+)$/;
534 51         74 my $method = $1;
535 51 100       76 if ($self->{__controller}->isMocked($method, @args)) {
536 46         76 return $self->{__controller}->_call($method, @args);
537             }
538             else {
539 5 100       25 if ($self->{__object}->can($method)) {
540 3         8 unshift @_, $self->{__object};
541 3         3 goto &{ ref($self->{__object}).'::'.$method };
  3         18  
542             }
543             else {
544 2         4 my $pack = ref($self->{__object});
545 2         218 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.