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 155     155   1054 use strict;
  155         316  
  155         4654  
3 155     155   829 use warnings;
  155         309  
  155         4113  
4              
5 155     155   1029 use Carp qw/croak/;
  155         317  
  155         7137  
6 155     155   904 use Scalar::Util qw/blessed reftype weaken/;
  155         338  
  155         8103  
7 155     155   901 use Test2::Util qw/try/;
  155         398  
  155         6985  
8 155     155   1313 use Test2::Util::Sub qw/gen_accessor gen_reader gen_writer/;
  155         402  
  155         13130  
9              
10 155     155   75679 use Test2::Mock();
  155         439  
  155         3644  
11              
12 155     155   1043 use base 'Exporter';
  155         376  
  155         198684  
13              
14             our $VERSION = '0.000155';
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 359 my $class = shift;
33 44         135 my ($for, $code) = @_;
34              
35 44 50       209 croak "Must specify a package for the mock handler"
36             unless $for;
37              
38 44 50 33     275 croak "Handlers must be code referneces (got: $code)"
39             unless $code && ref($code) eq 'CODE';
40              
41 44         141 push @{$HANDLERS{$for}} => $code;
  44         217  
42             }
43              
44             sub mock_building {
45 7 100   7 1 59 return unless @BUILD;
46 3         18 return $BUILD[-1];
47             }
48              
49             sub mocked {
50 53     53 1 706 my $proto = shift;
51 53   66     552 my $class = blessed($proto) || $proto;
52              
53             # Check if we have any mocks.
54 53   100     306 my $set = $MOCKS{$class} || return;
55              
56             # Remove dead mocks (undef due to weaken)
57 27   100     848 pop @$set while @$set && !defined($set->[-1]);
58              
59             # Remove the list if it is empty
60 27 100       382 delete $MOCKS{$class} unless @$set;
61              
62             # Return the controls (may be empty list)
63 27         245 return @$set;
64             }
65              
66             sub _delegate {
67 28     28   71 my ($args) = @_;
68              
69 28         348 my $do = __PACKAGE__->can('mock_do');
70 28         134 my $obj = __PACKAGE__->can('mock_obj');
71 28         122 my $class = __PACKAGE__->can('mock_class');
72 28         125 my $build = __PACKAGE__->can('mock_build');
73              
74 28 100       106 return $obj unless @$args;
75              
76 27         77 my ($proto, $arg1) = @$args;
77              
78 27 100 100     113 return $obj if ref($proto) && !blessed($proto);
79              
80 26 100       95 if (blessed($proto)) {
81 2 100       14 return $class unless $proto->isa('Test2::Mock');
82 1 50 33     14 return $build if $arg1 && ref($arg1) && reftype($arg1) eq 'CODE';
      33        
83             }
84              
85 24 100       215 return $class if $proto =~ m/(?:::|')/;
86 14 100       73 return $class if $proto =~ m/^_*[A-Z]/;
87              
88 3 100       22 return $do if Test2::Mock->can($proto);
89              
90 2 100       16 if (my $sub = __PACKAGE__->can("mock_$proto")) {
91 1         3 shift @$args;
92 1         4 return $sub;
93             }
94              
95 1         4 return undef;
96             }
97              
98             sub mock {
99 29 100 100 29 1 2169 croak "undef is not a valid first argument to mock()"
100             if @_ && !defined($_[0]);
101              
102 28         130 my $sub = _delegate(\@_);
103              
104 28 100       207 croak "'$_[0]' does not look like a package name, and is not a valid control method"
105             unless $sub;
106              
107 27         102 $sub->(@_);
108             }
109              
110             sub mock_build {
111 8     8 1 32 my ($control, $sub) = @_;
112              
113 8 50 66     427 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     216 croak "mock_build requires a coderef as its second argument"
      66        
117             unless $sub && ref($sub) && reftype($sub) eq 'CODE';
118              
119 4         10 push @BUILD => $control;
120 4         17 my ($ok, $err) = &try($sub);
121 4         50 pop @BUILD;
122 4 50       15 die $err unless $ok;
123             }
124              
125             sub mock_do {
126 7     7 1 39 my ($meth, @args) = @_;
127              
128 7 100       278 croak "Not currently building a mock"
129             unless @BUILD;
130              
131 5         10 my $build = $BUILD[-1];
132              
133 5 100 66     262 croak "'$meth' is not a valid action for mock_do()"
134             if $meth =~ m/^_/ || !$build->can($meth);
135              
136 3         15 $build->$meth(@args);
137             }
138              
139             sub mock_obj {
140 6     6 1 580 my ($proto) = @_;
141              
142 6 100 66     50 if ($proto && ref($proto) && reftype($proto) ne 'CODE') {
      100        
143 4         9 shift @_;
144             }
145             else {
146 2         5 $proto = {};
147             }
148              
149 6         16 my $class = _generate_class();
150 6         10 my $control;
151              
152 6 100 66     27 if (@_ == 1 && reftype($_[0]) eq 'CODE') {
153 1         3 my $orig = shift @_;
154             $control = mock_class(
155             $class,
156             sub {
157 1     1   13 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         4 $c->purge_on_destroy(1);
162 1         6 $c->autoload(1);
163              
164 1         7 $orig->(@_);
165             },
166 1         6 );
167             }
168             else {
169 5         18 $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         29 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         59 $new->{'~~MOCK~CONTROL~~'} = $control;
184 6         28 return $new;
185             }
186              
187             sub _generate_class {
188 6     6   12 my $prefix = __PACKAGE__;
189              
190 6         18 for (1 .. 100) {
191 6         15 my $postfix = join '', map { chr(rand(26) + 65) } 1 .. 32;
  192         338  
192 6         29 my $class = $prefix . '::__TEMP__::' . $postfix;
193 6         13 my $file = $class;
194 6         34 $file =~ s{::}{/}g;
195 6         13 $file .= '.pm';
196 6 50       46 next if $INC{$file};
197 155     155   1437 my $stash = do { no strict 'refs'; \%{"${class}\::"} };
  155         550  
  155         113881  
  6         10  
  6         9  
  6         50  
198 6 50       22 next if keys %$stash;
199 6         19 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 384 my $proto = shift;
207 33   66     212 my $class = blessed($proto) || $proto;
208 33         98 my @args = @_;
209              
210 33         76 my $void = !defined(wantarray);
211              
212             my $callback = sub {
213 39     39   757 my ($parent) = reverse mocked($class);
214 39         82 my $control;
215              
216 39 100 66     332 if (@args == 1 && ref($args[0]) && reftype($args[0]) eq 'CODE') {
      66        
217 1         4 $control = Test2::Mock->new(class => $class);
218 1         2 mock_build($control, @args);
219             }
220             else {
221 38         513 $control = Test2::Mock->new(class => $class, @args);
222             }
223              
224 39 100       171 if ($parent) {
225 3         11 $control->{parent} = $parent;
226 3         10 weaken($parent->{child} = $control);
227             }
228              
229 39   100     350 $MOCKS{$class} ||= [];
230 39         76 push @{$MOCKS{$class}} => $control;
  39         118  
231 39         199 weaken($MOCKS{$class}->[-1]);
232              
233 39         478 return $control;
234 33         236 };
235              
236 33 100       147 return $callback->() unless $void;
237              
238 13         32 my $level = 0;
239 13         29 my $caller;
240 13         158 while (my @call = caller($level++)) {
241 26 100       157 next if $call[0] eq __PACKAGE__;
242 13         30 $caller = \@call;
243 13         48 last;
244             }
245              
246 13         37 my $handled;
247 13         24 for my $handler (@{$HANDLERS{$caller->[0]}}) {
  13         69  
248 13 50       85 $handled++ if $handler->(
249             class => $class,
250             caller => $caller,
251             builder => $callback,
252             args => \@args,
253             );
254             }
255              
256 13 50       132 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 295 return map {( $_ => gen_accessor($_) )} @_;
  3         9  
262             }
263              
264             sub mock_accessor {
265 2     2 1 29 my ($field) = @_;
266 2         10 return gen_accessor($field);
267             }
268              
269             sub mock_getters {
270 1     1 1 7 my ($prefix, @list) = @_;
271 1         4 return map {( "$prefix$_" => gen_reader($_) )} @list;
  3         26  
272             }
273              
274             sub mock_getter {
275 1     1 1 12 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         4 return map {( "$prefix$_" => gen_writer($_) )} @list;
  3         10  
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__