File Coverage

blib/lib/Test2/Tools/Mock.pm
Criterion Covered Total %
statement 152 153 99.3
branch 50 60 83.3
condition 41 58 70.6
subroutine 27 27 100.0
pod 13 14 92.8
total 283 312 90.7


line stmt bran cond sub pod time code
1             package Test2::Tools::Mock;
2 154     154   1027 use strict;
  154         325  
  154         4349  
3 154     154   761 use warnings;
  154         322  
  154         3888  
4              
5 154     154   740 use Carp qw/croak/;
  154         306  
  154         7203  
6 154     154   946 use Scalar::Util qw/blessed reftype weaken/;
  154         365  
  154         7644  
7 154     154   964 use Test2::Util qw/try/;
  154         336  
  154         6933  
8 154     154   953 use Test2::Util::Sub qw/gen_accessor gen_reader gen_writer/;
  154         375  
  154         8906  
9              
10 154     154   67260 use Test2::Mock();
  154         422  
  154         3835  
11              
12 154     154   936 use base 'Exporter';
  154         482  
  154         186851  
13              
14             our $VERSION = '0.000153';
15              
16             our @CARP_NOT = (__PACKAGE__, 'Test2::Mock');
17             our @EXPORT = qw/mock mocked/;
18             our @EXPORT_OK = qw{
19             mock_obj mock_class
20             mock_do mock_build
21             mock_accessor mock_accessors
22             mock_getter mock_getters
23             mock_setter mock_setters
24             mock_building
25             };
26              
27             my %HANDLERS;
28             my %MOCKS;
29             my @BUILD;
30              
31             sub add_handler {
32 44     44 0 250 my $class = shift;
33 44         113 my ($for, $code) = @_;
34              
35 44 50       145 croak "Must specify a package for the mock handler"
36             unless $for;
37              
38 44 50 33     286 croak "Handlers must be code referneces (got: $code)"
39             unless $code && ref($code) eq 'CODE';
40              
41 44         83 push @{$HANDLERS{$for}} => $code;
  44         186  
42             }
43              
44             sub mock_building {
45 7 100   7 1 39 return unless @BUILD;
46 3         9 return $BUILD[-1];
47             }
48              
49             sub mocked {
50 61     61 1 513 my $proto = shift;
51 61   66     423 my $class = blessed($proto) || $proto;
52              
53             # Check if we have any mocks.
54 61   100     307 my $set = $MOCKS{$class} || return;
55              
56             # Remove dead mocks (undef due to weaken)
57 35   100     498 pop @$set while @$set && !defined($set->[-1]);
58              
59             # Remove the list if it is empty
60 35 100       227 delete $MOCKS{$class} unless @$set;
61              
62             # Return the controls (may be empty list)
63 35         117 return @$set;
64             }
65              
66             sub _delegate {
67 28     28   73 my ($args) = @_;
68              
69 28         412 my $do = __PACKAGE__->can('mock_do');
70 28         98 my $obj = __PACKAGE__->can('mock_obj');
71 28         127 my $class = __PACKAGE__->can('mock_class');
72 28         101 my $build = __PACKAGE__->can('mock_build');
73              
74 28 100       94 return $obj unless @$args;
75              
76 27         70 my ($proto, $arg1) = @$args;
77              
78 27 100 100     122 return $obj if ref($proto) && !blessed($proto);
79              
80 26 100       125 if (blessed($proto)) {
81 2 100       12 return $class unless $proto->isa('Test2::Mock');
82 1 50 33     11 return $build if $arg1 && ref($arg1) && reftype($arg1) eq 'CODE';
      33        
83             }
84              
85 24 100       222 return $class if $proto =~ m/(?:::|')/;
86 14 100       61 return $class if $proto =~ m/^_*[A-Z]/;
87              
88 3 100       15 return $do if Test2::Mock->can($proto);
89              
90 2 100       15 if (my $sub = __PACKAGE__->can("mock_$proto")) {
91 1         2 shift @$args;
92 1         3 return $sub;
93             }
94              
95 1         2 return undef;
96             }
97              
98             sub mock {
99 29 100 100 29 1 1982 croak "undef is not a valid first argument to mock()"
100             if @_ && !defined($_[0]);
101              
102 28         138 my $sub = _delegate(\@_);
103              
104 28 100       186 croak "'$_[0]' does not look like a package name, and is not a valid control method"
105             unless $sub;
106              
107 27         96 $sub->(@_);
108             }
109              
110             sub mock_build {
111 8     8 1 28 my ($control, $sub) = @_;
112              
113 8 50 66     317 croak "mock_build requires a Test2::Mock object as its first argument"
      66        
114             unless $control && blessed($control) && $control->isa('Test2::Mock');
115              
116 6 50 66     186 croak "mock_build requires a coderef as its second argument"
      66        
117             unless $sub && ref($sub) && reftype($sub) eq 'CODE';
118              
119 4         8 push @BUILD => $control;
120 4         11 my ($ok, $err) = &try($sub);
121 4         34 pop @BUILD;
122 4 50       12 die $err unless $ok;
123             }
124              
125             sub mock_do {
126 7     7 1 27 my ($meth, @args) = @_;
127              
128 7 100       223 croak "Not currently building a mock"
129             unless @BUILD;
130              
131 5         8 my $build = $BUILD[-1];
132              
133 5 100 66     200 croak "'$meth' is not a valid action for mock_do()"
134             if $meth =~ m/^_/ || !$build->can($meth);
135              
136 3         10 $build->$meth(@args);
137             }
138              
139             sub mock_obj {
140 6     6 1 428 my ($proto) = @_;
141              
142 6 100 66     35 if ($proto && ref($proto) && reftype($proto) ne 'CODE') {
      100        
143 4         8 shift @_;
144             }
145             else {
146 2         3 $proto = {};
147             }
148              
149 6         10 my $class = _generate_class();
150 6         7 my $control;
151              
152 6 100 66     21 if (@_ == 1 && reftype($_[0]) eq 'CODE') {
153 1         3 my $orig = shift @_;
154             $control = mock_class(
155             $class,
156             sub {
157 1     1   11 my $c = mock_building;
158              
159             # We want to do these BEFORE anything that the sub may do.
160 1         4 $c->block_load(1);
161 1         3 $c->purge_on_destroy(1);
162 1         3 $c->autoload(1);
163              
164 1         5 $orig->(@_);
165             },
166 1         4 );
167             }
168             else {
169 5         12 $control = mock_class(
170             $class,
171             # Do these before anything the user specified.
172             block_load => 1,
173             purge_on_destroy => 1,
174             autoload => 1,
175             @_,
176             );
177             }
178              
179 6         24 my $new = bless($proto, $control->class);
180              
181             # We need to ensure there is a reference to the control object, and we want
182             # it to go away with the object.
183 6         42 $new->{'~~MOCK~CONTROL~~'} = $control;
184 6         21 return $new;
185             }
186              
187             sub _generate_class {
188 6     6   7 my $prefix = __PACKAGE__;
189              
190 6         14 for (1 .. 100) {
191 6         9 my $postfix = join '', map { chr(rand(26) + 65) } 1 .. 32;
  192         266  
192 6         26 my $class = $prefix . '::__TEMP__::' . $postfix;
193 6         10 my $file = $class;
194 6         25 $file =~ s{::}{/}g;
195 6         10 $file .= '.pm';
196 6 50       15 next if $INC{$file};
197 154     154   1259 my $stash = do { no strict 'refs'; \%{"${class}\::"} };
  154         466  
  154         103683  
  6         6  
  6         8  
  6         37  
198 6 50       15 next if keys %$stash;
199 6         15 return $class;
200             }
201              
202 0         0 croak "Could not generate a unique class name after 100 attempts";
203             }
204              
205             sub mock_class {
206 33     33 1 303 my $proto = shift;
207 33   66     192 my $class = blessed($proto) || $proto;
208 33         101 my @args = @_;
209              
210 33         78 my $void = !defined(wantarray);
211              
212             my $callback = sub {
213 47     47   650 my ($parent) = reverse mocked($class);
214 47         111 my $control;
215              
216 47 100 66     278 if (@args == 1 && ref($args[0]) && reftype($args[0]) eq 'CODE') {
      66        
217 1         5 $control = Test2::Mock->new(class => $class);
218 1         3 mock_build($control, @args);
219             }
220             else {
221 46         507 $control = Test2::Mock->new(class => $class, @args);
222             }
223              
224 47 100       154 if ($parent) {
225 3         4 $control->{parent} = $parent;
226 3         13 weaken($parent->{child} = $control);
227             }
228              
229 47   100     274 $MOCKS{$class} ||= [];
230 47         113 push @{$MOCKS{$class}} => $control;
  47         127  
231 47         190 weaken($MOCKS{$class}->[-1]);
232              
233 47         327 return $control;
234 33         261 };
235              
236 33 100       116 return $callback->() unless $void;
237              
238 13         37 my $level = 0;
239 13         19 my $caller;
240 13         171 while (my @call = caller($level++)) {
241 26 100       194 next if $call[0] eq __PACKAGE__;
242 13         50 $caller = \@call;
243 13         88 last;
244             }
245              
246 13         61 my $handled;
247 13         35 for my $handler (@{$HANDLERS{$caller->[0]}}) {
  13         95  
248 13 50       69 $handled++ if $handler->(
249             class => $class,
250             caller => $caller,
251             builder => $callback,
252             args => \@args,
253             );
254             }
255              
256 13 50       117 croak "mock_class should not be called in a void context without a registered handler"
257             unless $handled;
258             }
259              
260             sub mock_accessors {
261 1     1 1 208 return map {( $_ => gen_accessor($_) )} @_;
  3         6  
262             }
263              
264             sub mock_accessor {
265 2     2 1 14 my ($field) = @_;
266 2         8 return gen_accessor($field);
267             }
268              
269             sub mock_getters {
270 1     1 1 4 my ($prefix, @list) = @_;
271 1         2 return map {( "$prefix$_" => gen_reader($_) )} @list;
  3         5  
272             }
273              
274             sub mock_getter {
275 1     1 1 11 my ($field) = @_;
276 1         4 return gen_reader($field);
277             }
278              
279             sub mock_setters {
280 1     1 1 4 my ($prefix, @list) = @_;
281 1         2 return map {( "$prefix$_" => gen_writer($_) )} @list;
  3         8  
282             }
283              
284             sub mock_setter {
285 1     1 1 16 my ($field) = @_;
286 1         4 return gen_writer($field);
287             }
288              
289             1;
290              
291             __END__