File Coverage

blib/lib/Test/MockObject.pm
Criterion Covered Total %
statement 209 209 100.0
branch 47 52 90.3
condition 30 41 73.1
subroutine 61 61 100.0
pod 26 26 100.0
total 373 389 95.8


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