File Coverage

inc/Test/MockObject.pm
Criterion Covered Total %
statement 97 211 45.9
branch 9 50 18.0
condition 9 41 21.9
subroutine 31 63 49.2
pod 26 26 100.0
total 172 391 43.9


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