File Coverage

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


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