File Coverage

inc/Test/MockObject.pm
Criterion Covered Total %
statement 102 208 49.0
branch 12 52 23.0
condition 8 41 19.5
subroutine 35 62 56.4
pod 26 26 100.0
total 183 389 47.0


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