File Coverage

blib/lib/Test/MockObject.pm
Criterion Covered Total %
statement 211 211 100.0
branch 47 52 90.3
condition 31 41 75.6
subroutine 61 61 100.0
pod 26 26 100.0
total 376 391 96.1


line stmt bran cond sub pod time code
1             package Test::MockObject;
2             $Test::MockObject::VERSION = '1.20150527';
3 11     11   47058 use strict;
  11         25  
  11         473  
4 11     11   57 use warnings;
  11         14  
  11         435  
5              
6 11     11   69 use Scalar::Util qw( blessed refaddr reftype weaken );
  11         12  
  11         2468  
7              
8             sub import
9             {
10 20     20   8966 my $self = shift;
11 20 100       2231 return unless grep /^-debug/, @_;
12 2         4 eval {
13 2         3083 require UNIVERSAL::isa;
14 2         3714 UNIVERSAL::isa::->import('verbose');
15             };
16 2         42 eval {
17 2         1143 require UNIVERSAL::can;
18 2         1909 UNIVERSAL::can::->import('-always_warn');
19             };
20             }
21              
22 11     11   68 use Test::Builder;
  11         16  
  11         6963  
23              
24             my $Test = Test::Builder->new();
25             my (%calls, %subs);
26              
27             sub new
28             {
29 19     19 1 3091 my ($class, $type) = @_;
30 19   50     93 $type ||= {};
31 19         56 bless $type, $class;
32             }
33              
34             sub mock
35             {
36 23     23 1 310 my ($self, $name, $sub) = @_;
37 23   100 1   51 $sub ||= sub {};
  1         3  
38              
39             # leading dash means unlog, otherwise do log
40 23 100       66 _set_log( $self, $name, ( $name =~ s/^-// ? 0 : 1 ) );
41 23         29 _subs( $self )->{$name} = $sub;
42              
43 23         51 $self;
44             }
45              
46             sub set_isa
47             {
48 2     2 1 606 my ($self, @supers) = @_;
49 2         24 my $supers = _isas( $self );
50 2         470 $supers->{$_} = 1 for @supers;
51             }
52              
53             sub set_always
54             {
55 7     7 1 22 my ($self, $name, $value) = @_;
56 7     9   40 $self->mock( $name, sub { $value } );
  9         29  
57             }
58              
59             sub set_true
60             {
61 5     5 1 18 my $self = shift;
62              
63 5         10 for my $name ( @_ )
64             {
65 8     8   28 $self->mock( $name, sub { 1 } );
  8         26  
66             }
67              
68 5         9 return $self;
69             }
70              
71             sub set_false
72             {
73 3     3 1 504 my $self = shift;
74              
75 3         7 for my $name ( @_ )
76             {
77 5     6   18 $self->mock( $name, sub {} );
  6         17  
78             }
79              
80 3         6 return $self;
81             }
82              
83             sub set_list
84             {
85 1     1 1 2 my ($self, $name, @list) = @_;
86 1     4   5 $self->mock( $name, sub { @{[ @list ]} } );
  4         5  
  4         12  
87             }
88              
89             sub set_series
90             {
91 2     2 1 8 my ($self, $name, @list) = @_;
92 2 100   7   9 $self->mock( $name, sub { return unless @list; shift @list } );
  7         18  
  5         15  
93             }
94              
95             sub set_bound
96             {
97 3     3 1 5 my ($self, $name, $ref) = @_;
98              
99             my %bindings =
100             (
101 2     2   6 SCALAR => sub { $$ref },
102 1     1   6 ARRAY => sub { @$ref },
103 1     1   12 HASH => sub { %$ref },
104 3         27 );
105              
106 3 50       14 return unless exists $bindings{reftype( $ref )};
107 3         9 $self->mock( $name, $bindings{reftype( $ref )} );
108             }
109              
110             # hack around debugging mode being too smart for my sub names
111             my $old_p;
112             BEGIN
113             {
114 11     11   45 $old_p = $^P;
115 11         1587 $^P &= ~0x200;
116             }
117              
118             BEGIN
119             {
120 11     11   108 for my $universal
121             ( { sub => \&_subs, name => 'can' }, { sub => \&_isas, name => 'isa' } )
122             {
123             my $sub = sub
124             {
125 59     59   3863 my ($self, $sub) = @_;
126 59         146 local *__ANON__ = $universal->{name};
127              
128             # mockmethods are special cases, class methods are handled directly
129 59         121 my $lookup = $universal->{sub}->( $self );
130 59 100 100     238 return $lookup->{$sub} if blessed $self and exists $lookup->{$sub};
131 53         107 my $parent = 'SUPER::' . $universal->{name};
132 53         432 return $self->$parent( $sub );
133 22         95 };
134              
135 11     11   65 no strict 'refs';
  11         22  
  11         620  
136 22         30 *{ $universal->{name} } = $sub;
  22         135  
137             }
138              
139 11         12947 $^P = $old_p;
140             }
141              
142             sub remove
143             {
144 1     1 1 1 my ($self, $sub) = @_;
145 1         2 delete _subs( $self )->{$sub};
146 1         2 $self;
147             }
148              
149             sub called
150             {
151 9     9 1 796 my ($self, $sub) = @_;
152              
153 9         11 for my $called (reverse @{ _calls( $self ) })
  9         16  
154             {
155 28 100       58 return 1 if $called->[0] eq $sub;
156             }
157              
158 6         20 return 0;
159             }
160              
161             sub clear
162             {
163 2     2 1 209 my $self = shift;
164 2         2 @{ _calls( $self ) } = ();
  2         4  
165 2         5 $self;
166             }
167              
168             sub call_pos
169             {
170 5     5 1 13 $_[0]->_call($_[1], 0);
171             }
172              
173             sub call_args
174             {
175 2     2 1 304 return @{ $_[0]->_call($_[1], 1) };
  2         4  
176             }
177              
178             sub _call
179             {
180 15     15   27 my ($self, $pos, $type) = @_;
181 15         21 my $calls = _calls( $self );
182 15 100       39 return if abs($pos) > @$calls;
183 12 100       24 $pos-- if $pos > 0;
184 12         37 return $calls->[$pos][$type];
185             }
186              
187             sub call_args_string
188             {
189 3 50   3 1 7 my $args = $_[0]->_call( $_[1], 1 ) or return;
190 3   100     31 return join($_[2] || '', @$args);
191             }
192              
193             sub call_args_pos
194             {
195 4     4 1 10 my ($self, $subpos, $argpos) = @_;
196 4 50       8 my $args = $self->_call( $subpos, 1 ) or return;
197 4 100       9 $argpos-- if $argpos > 0;
198 4         12 return $args->[$argpos];
199             }
200              
201             sub next_call
202             {
203 6     6 1 276 my ($self, $num) = @_;
204 6   100     22 $num ||= 1;
205              
206 6         10 my $calls = _calls( $self );
207 6 100       19 return unless @$calls >= $num;
208              
209 5         13 my ($call) = (splice(@$calls, 0, $num))[-1];
210 5 100       24 return wantarray() ? @$call : $call->[0];
211             }
212              
213             sub AUTOLOAD
214             {
215 40     40   1354 our $AUTOLOAD;
216              
217 40         39 my $self = shift;
218 40         435 my $sub;
219             {
220 40         33 local $1;
  40         57  
221 40         216 ($sub) = $AUTOLOAD =~ /::(\w+)\z/;
222             }
223 40 50       105 return if $sub eq 'DESTROY';
224              
225 40         77 $self->dispatch_mocked_method( $sub, @_ );
226             }
227              
228             sub dispatch_mocked_method
229             {
230 40     40 1 43 my $self = $_[0];
231 40         55 my $sub = splice( @_, 1, 1 );
232              
233 40         48 my $subs = _subs( $self );
234 40 100       75 if (exists $subs->{$sub})
235             {
236 39         65 $self->log_call( $sub, @_ );
237 39         39 goto &{ $subs->{$sub} };
  39         97  
238             }
239             else
240             {
241 1         5 require Carp;
242 1         5 Carp::carp("Un-mocked method '$sub()' called");
243             }
244              
245 1         3 return;
246             }
247              
248             sub log_call
249             {
250 45     45 1 131 my ($self, $sub, @call_args) = @_;
251 45 100       65 return unless _logs( $self, $sub );
252              
253             # prevent circular references with weaken
254 42         63 for my $arg ( @call_args )
255             {
256 53 100       84 next unless ref $arg;
257 45 100       178 weaken( $arg ) if refaddr( $arg ) eq refaddr( $self );
258             }
259              
260 42         38 push @{ _calls( $self ) }, [ $sub, \@call_args ];
  42         59  
261             }
262              
263             sub called_ok
264             {
265 2     2 1 33 my ($self, $sub, $name) = @_;
266 2   33     14 $name ||= "object called '$sub'";
267 2         6 $Test->ok( $self->called($sub), $name );
268             }
269              
270             sub called_pos_ok
271             {
272 3     3 1 17 my ($self, $pos, $sub, $name) = @_;
273 3   33     16 $name ||= "object called '$sub' at position $pos";
274 3         17 my $called = $self->call_pos($pos, $sub);
275 3 100 66     15 unless ($Test->ok( (defined $called and $called eq $sub), $name ))
276             {
277 2 50       9 $called = 'undef' unless defined $called;
278 2         6 $Test->diag("Got:\n\t'$called'\nExpected:\n\t'$sub'\n");
279             }
280             }
281              
282             sub called_args_string_is
283             {
284 1     1 1 2 my ($self, $pos, $sep, $expected, $name) = @_;
285 1   33     6 $name ||= "object sent expected args to sub at position $pos";
286 1         2 $Test->is_eq( $self->call_args_string( $pos, $sep ), $expected, $name );
287             }
288              
289             sub called_args_pos_is
290             {
291 1     1 1 2 my ($self, $pos, $argpos, $arg, $name) = @_;
292 1   33     7 $name ||= "object sent expected arg '$arg' to sub at position $pos";
293 1         2 $Test->is_eq( $self->call_args_pos( $pos, $argpos ), $arg, $name );
294             }
295              
296             sub fake_module
297             {
298 8     8 1 36 my ($class, $modname, %subs) = @_;
299              
300 8 100 100     25 if ($class->check_class_loaded( $modname ) and ! keys %subs)
301             {
302 1         8 require Carp;
303 1         107 Carp::croak( "No mocked subs for loaded module '$modname'" );
304             }
305              
306 7         10 $modname =~ s!::!/!g;
307 7         13 $INC{ $modname . '.pm' } = 1;
308              
309 11     11   80 no warnings 'redefine';
  11         486  
  11         616  
310             {
311 11     11   58 no strict 'refs';
  11         15  
  11         1341  
  7         6  
312 7   100     7 ${ $modname . '::' }{VERSION} ||= -1;
  7         35  
313             }
314              
315 7         16 for my $sub (keys %subs)
316             {
317 5   100     21 my $type = reftype( $subs{ $sub } ) || '';
318 5 100       9 unless ( $type eq 'CODE' )
319             {
320 1         6 require Carp;
321 1         119 Carp::carp("'$sub' is not a code reference" );
322 1         62 next;
323             }
324 11     11   60 no strict 'refs';
  11         16  
  11         7399  
325 4         5 *{ $_[1] . '::' . $sub } = $subs{ $sub };
  4         28  
326             }
327             }
328              
329             sub check_class_loaded
330             {
331 30     30 1 47 my ($self, $class, $load_flag) = @_;
332              
333 30         76 (my $path = $class) =~ s{::}{/}g;
334 30 100       127 return 1 if exists $INC{ $path . '.pm' };
335              
336 17         27 my $symtable = \%main::;
337 17         22 my $found = 1;
338              
339 17         71 for my $symbol ( split( '::', $class ))
340             {
341 23 100       62 unless (exists $symtable->{ $symbol . '::' })
342             {
343 7         6 $found = 0;
344 7         11 last;
345             }
346              
347 16         56 $symtable = $symtable->{ $symbol . '::' };
348             }
349              
350 17         65 return $found;
351             }
352              
353             sub fake_new
354             {
355 1     1 1 217 my ($self, $class) = @_;
356 1     1   5 $self->fake_module( $class, new => sub { $self } );
  1         6  
357             }
358              
359             sub DESTROY
360             {
361 18     18   3975 my $self = shift;
362 18         41 $self->_clear_calls();
363 18         56 $self->_clear_subs();
364 18         35 $self->_clear_logs();
365 18         29 $self->_clear_isas();
366             }
367              
368             sub _get_key
369             {
370 351     351   319 my $invocant = shift;
371 351 100       2730 return blessed( $invocant ) ? refaddr( $invocant ) : $invocant;
372             }
373              
374             {
375             my %calls;
376              
377             sub _calls
378             {
379 76   100 76   333 $calls{ _get_key( shift ) } ||= [];
380             }
381              
382             sub _clear_calls
383             {
384 18     18   38 delete $calls{ _get_key( shift ) };
385             }
386             }
387              
388             {
389             my %subs;
390              
391             sub _subs
392             {
393 117   100 117   408 $subs{ _get_key( shift ) } ||= {};
394             }
395              
396             sub _clear_subs
397             {
398 18     18   27 delete $subs{ _get_key( shift ) };
399             }
400             }
401              
402             {
403             my %logs;
404              
405             sub _set_log
406             {
407 31     31   49 my $key = _get_key( shift );
408 31         42 my ($name, $log) = @_;
409              
410 31   100     111 $logs{$key} ||= {};
411              
412 31 100       71 if ($log)
413             {
414 27         56 $logs{$key}{$name} = 1;
415             }
416             else
417             {
418 4         11 delete $logs{$key}{$name};
419             }
420             }
421              
422             sub _logs
423             {
424 45     45   56 my $key = _get_key( shift );
425 45         51 my ($name) = @_;
426 45         119 return exists $logs{$key}{$name};
427             }
428              
429             sub _clear_logs
430             {
431 18     18   34 delete $logs{ _get_key( shift ) };
432             }
433             }
434              
435             {
436             my %isas;
437              
438             sub _isas
439             {
440 10   100 10   21 $isas{ _get_key( shift ) } ||= {};
441             }
442              
443             sub _clear_isas
444             {
445 18     18   26 delete $isas{ _get_key( shift ) };
446             }
447             }
448              
449             1;
450              
451             __END__