File Coverage

blib/lib/Test/MonkeyMock.pm
Criterion Covered Total %
statement 137 139 98.5
branch 53 64 82.8
condition 26 30 86.6
subroutine 18 18 100.0
pod 8 8 100.0
total 242 259 93.4


line stmt bran cond sub pod time code
1             package Test::MonkeyMock;
2              
3 2     2   129290 use strict;
  2         13  
  2         48  
4 2     2   9 use warnings;
  2         3  
  2         169  
5              
6             require Carp;
7              
8             our $VERSION = '0.10';
9              
10             my $registry = {};
11             my $magic_counter = 0;
12              
13             sub new {
14 33     33 1 67076 my $class = shift;
15 33 50       88 $class = ref $class if ref $class;
16 33         74 my ($instance) = @_;
17              
18 33         45 my $new_package;
19              
20 33 100       67 if ($instance) {
21 19         46 $new_package =
22             __PACKAGE__ . '::'
23             . ref($instance)
24             . '::__instance__'
25             . ($magic_counter++);
26              
27 2     2   11 no strict 'refs';
  2         3  
  2         121  
28 19         43 @{$new_package . '::ISA'} = (ref($instance));
  19         278  
29             }
30             else {
31 14         19 $instance = {};
32 14         32 $new_package = __PACKAGE__ . '::' . ($magic_counter++);
33              
34 2     2   9 no strict 'refs';
  2         4  
  2         75  
35 14         16 @{$new_package . '::ISA'} = __PACKAGE__;
  14         193  
36             }
37              
38 2     2   9 no strict 'refs';
  2         3  
  2         481  
39 33         81 for my $method (
40             qw/
41             mock
42             mocked_called
43             mocked_call_args
44             mocked_call_stack
45             mocked_return_args
46             mocked_return_stack
47             /
48             )
49             {
50 198     74   525 *{$new_package . '::' . $method} = sub { goto &$method };
  198         795  
  74         2027  
51             }
52              
53 33         92 bless $instance, $new_package;
54              
55 33         99 return $instance;
56             }
57              
58             sub mock {
59 34     34 1 50 my $self = shift;
60 34         69 my ($method, $code, %options) = @_;
61              
62 34 100       110 if (ref($self) =~ m/__instance__/) {
63 17 100       260 Carp::croak("Unknown method '$method'")
64             unless my $orig_method = $self->can($method);
65              
66 16 100       66 if (exists $registry->{ref($self)}->{'mocks'}->{$method}) {
67 1         3 push @{$registry->{ref($self)}->{'mocks'}->{$method}},
  1         7  
68             {code => $code, orig_code => $orig_method};
69 1         5 return $self;
70             }
71              
72 15         26 my $ref_self = ref($self);
73 15         21 my $package = __PACKAGE__;
74 15         145 $ref_self =~ s/^${package}::(.*)::__instance__\d+/$1/;
75              
76 15         73 my $new_package =
77             __PACKAGE__ . '::' . $ref_self . '::__instance__' . $magic_counter++;
78              
79 15         41 $registry->{$new_package} = $registry->{ref($self)};
80              
81 15   50     44 my $mocks = $registry->{$new_package}->{'mocks'} ||= {};
82 15         52 $mocks->{$method} =
83             [{code => $code, orig_code => $orig_method, options => \%options}];
84              
85 2     2   28 no strict 'refs';
  2         4  
  2         2369  
86 15         26 @{$new_package . '::ISA'} = ref($self);
  15         200  
87 15         67 *{$new_package . '::' . $method} = sub {
88 26     26   120 _dispatch($new_package, $method, @_);
89 15         58 };
90              
91 15         59 bless $self, $new_package;
92             }
93             else {
94 17   100     74 my $mocks = $registry->{ref($self)}->{'mocks'} ||= {};
95 17         25 push @{$mocks->{$method}},
  17         60  
96             {
97             code => $code,
98             options => \%options
99             };
100             }
101              
102 32         82 return $self;
103             }
104              
105             sub mocked_called {
106 6     6 1 16 my $self = shift;
107 6         16 my ($method) = @_;
108              
109 6   100     27 my $mocks = $registry->{ref($self)}->{'mocks'} ||= {};
110 6   100     24 my $calls = $registry->{ref($self)}->{'calls'} ||= {};
111              
112 6 100       23 if (ref($self) =~ m/__instance__/) {
113 3 100       87 Carp::croak("Unknown method '$method'")
114             unless $self->can($method);
115             }
116             else {
117             Carp::croak("Unmocked method '$method'")
118 3 100       68 unless exists $mocks->{$method};
119             }
120              
121 4   100     24 return $calls->{$method}->{called} || 0;
122             }
123              
124             sub mocked_call_args {
125 10     10 1 20 my $self = shift;
126 10         24 my ($method, $frame) = @_;
127              
128 10   100     45 $frame ||= 0;
129              
130 10         25 my $stack = $self->mocked_call_stack($method);
131              
132 8 100       311 Carp::croak("Unknown frame '$frame'")
133             unless @$stack > $frame;
134              
135 6         11 return @{$stack->[$frame]};
  6         44  
136             }
137              
138             sub mocked_call_stack {
139 11     11 1 21 my $self = shift;
140 11         24 my ($method) = @_;
141              
142 11 50       31 Carp::croak("Method is required") unless $method;
143              
144 11   100     52 my $calls = $registry->{ref($self)}->{'calls'} ||= {};
145 11   100     37 my $mocks = $registry->{ref($self)}->{'mocks'} ||= {};
146              
147 11 100       45 if (ref($self) =~ m/__instance__/) {
148 6 100       109 Carp::croak("Unknown method '$method'")
149             unless $self->can($method);
150             }
151             else {
152             Carp::croak("Unmocked method '$method'")
153 5 100       73 unless exists $mocks->{$method};
154             }
155              
156             Carp::croak("Method '$method' was not called")
157 9 50       24 unless exists $calls->{$method};
158              
159 9         29 return $calls->{$method}->{stack};
160             }
161              
162             sub mocked_return_args {
163 6     6 1 11 my $self = shift;
164 6         16 my ($method, $frame) = @_;
165              
166 6   100     20 $frame ||= 0;
167              
168 6         16 my $stack = $self->mocked_return_stack($method);
169              
170 6 50       14 Carp::croak("Unknown frame '$frame'")
171             unless @$stack > $frame;
172              
173 6         8 return @{$stack->[$frame]};
  6         27  
174             }
175              
176             sub mocked_return_stack {
177 7     7 1 11 my $self = shift;
178 7         12 my ($method) = @_;
179              
180 7 50       14 Carp::croak("Method is required") unless $method;
181              
182 7   50     26 my $returns = $registry->{ref($self)}->{'returns'} ||= {};
183 7   50     16 my $mocks = $registry->{ref($self)}->{'mocks'} ||= {};
184              
185 7 100       25 if (ref($self) =~ m/__instance__/) {
186 4 50       15 Carp::croak("Unknown method '$method'")
187             unless $self->can($method);
188             }
189             else {
190             Carp::croak("Unmocked method '$method'")
191 3 50       7 unless exists $mocks->{$method};
192             }
193              
194             Carp::croak("Method '$method' was not called")
195 7 50       14 unless exists $returns->{$method};
196              
197 7         19 return $returns->{$method}->{stack};
198             }
199              
200             sub can {
201 5     5 1 16 my $self = shift;
202 5         12 my ($method) = @_;
203              
204 5 50       13 if (ref($self) =~ m/__instance__/) {
205 0         0 return $self->can($method);
206             }
207             else {
208 5   50     15 my $mocks = $registry->{ref($self)}->{'mocks'} ||= {};
209 5         72 return $mocks->{$method}->[0]->{code};
210             }
211             }
212              
213             our $AUTOLOAD;
214              
215             sub AUTOLOAD {
216 33     33   7493 my $self = shift;
217              
218 33         113 my ($method) = (split /::/, $AUTOLOAD)[-1];
219              
220 33 100       153 return if $method =~ /^[A-Z]+$/;
221              
222 19         51 return _dispatch(ref($self), $method, $self, @_);
223             }
224              
225             sub _dispatch {
226 45     45   70 my $ref_self = shift;
227 45         71 my $method = shift;
228              
229 45   100     155 my $calls = $registry->{$ref_self}->{'calls'} ||= {};
230 45   100     115 my $returns = $registry->{$ref_self}->{'returns'} ||= {};
231 45   100     98 my $mocks = $registry->{$ref_self}->{'mocks'} ||= {};
232              
233             Carp::croak("Unmocked method '$method'")
234 45 100       163 if !exists $mocks->{$method};
235              
236 44         60 foreach my $mock (@{$mocks->{$method}}) {
  44         94  
237 51 100       104 if (my $options = $mock->{options}) {
238 50 100       91 if (my $when = $options->{when}) {
239 4 100       11 next unless $when->(@_);
240             }
241              
242 48 100       106 if (defined(my $frame = $options->{frame})) {
243             my $current_frame =
244             $returns->{$method}->{stack}
245 7 100       16 ? @{$returns->{$method}->{stack}}
  6         10  
246             : 0;
247 7 100       18 next unless $frame == $current_frame;
248             }
249             }
250              
251 44         84 $calls->{$method}->{called}++;
252              
253 44         55 push @{$calls->{$method}->{stack}}, [@_[1 .. $#_]];
  44         137  
254              
255 44         71 my @result;
256              
257 44 100       78 if (my $code = $mock->{code}) {
    50          
258 41         145 @result = $code->(@_);
259             }
260             elsif (my $orig_code = $mock->{orig_code}) {
261 3         7 @result = $orig_code->(@_);
262             }
263             else {
264 0         0 Carp::croak("Unmocked method '$method'");
265             }
266              
267 44         144 push @{$returns->{$method}->{stack}}, [@result];
  44         132  
268              
269 44 50       174 return wantarray ? @result : $result[0];
270             }
271             }
272              
273             1;
274             __END__