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   1131 use strict;
  155         356  
  155         4709  
3 155     155   846 use warnings;
  155         324  
  155         4169  
4              
5 155     155   844 use Carp qw/croak/;
  155         370  
  155         7354  
6 155     155   940 use Scalar::Util qw/blessed reftype weaken/;
  155         429  
  155         7912  
7 155     155   1038 use Test2::Util qw/try/;
  155         538  
  155         7738  
8 155     155   1091 use Test2::Util::Sub qw/gen_accessor gen_reader gen_writer/;
  155         423  
  155         10215  
9              
10 155     155   78860 use Test2::Mock();
  155         441  
  155         3789  
11              
12 155     155   1088 use base 'Exporter';
  155         338  
  155         208987  
13              
14             our $VERSION = '0.000156';
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 341 my $class = shift;
33 44         132 my ($for, $code) = @_;
34              
35 44 50       167 croak "Must specify a package for the mock handler"
36             unless $for;
37              
38 44 50 33     293 croak "Handlers must be code referneces (got: $code)"
39             unless $code && ref($code) eq 'CODE';
40              
41 44         113 push @{$HANDLERS{$for}} => $code;
  44         194  
42             }
43              
44             sub mock_building {
45 7 100   7 1 54 return unless @BUILD;
46 3         22 return $BUILD[-1];
47             }
48              
49             sub mocked {
50 61     61 1 674 my $proto = shift;
51 61   66     350 my $class = blessed($proto) || $proto;
52              
53             # Check if we have any mocks.
54 61   100     309 my $set = $MOCKS{$class} || return;
55              
56             # Remove dead mocks (undef due to weaken)
57 35   100     582 pop @$set while @$set && !defined($set->[-1]);
58              
59             # Remove the list if it is empty
60 35 100       172 delete $MOCKS{$class} unless @$set;
61              
62             # Return the controls (may be empty list)
63 35         163 return @$set;
64             }
65              
66             sub _delegate {
67 28     28   64 my ($args) = @_;
68              
69 28         358 my $do = __PACKAGE__->can('mock_do');
70 28         171 my $obj = __PACKAGE__->can('mock_obj');
71 28         132 my $class = __PACKAGE__->can('mock_class');
72 28         90 my $build = __PACKAGE__->can('mock_build');
73              
74 28 100       79 return $obj unless @$args;
75              
76 27         75 my ($proto, $arg1) = @$args;
77              
78 27 100 100     134 return $obj if ref($proto) && !blessed($proto);
79              
80 26 100       103 if (blessed($proto)) {
81 2 100       17 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       206 return $class if $proto =~ m/(?:::|')/;
86 14 100       81 return $class if $proto =~ m/^_*[A-Z]/;
87              
88 3 100       26 return $do if Test2::Mock->can($proto);
89              
90 2 100       15 if (my $sub = __PACKAGE__->can("mock_$proto")) {
91 1         3 shift @$args;
92 1         4 return $sub;
93             }
94              
95 1         5 return undef;
96             }
97              
98             sub mock {
99 29 100 100 29 1 2465 croak "undef is not a valid first argument to mock()"
100             if @_ && !defined($_[0]);
101              
102 28         151 my $sub = _delegate(\@_);
103              
104 28 100       197 croak "'$_[0]' does not look like a package name, and is not a valid control method"
105             unless $sub;
106              
107 27         146 $sub->(@_);
108             }
109              
110             sub mock_build {
111 8     8 1 32 my ($control, $sub) = @_;
112              
113 8 50 66     390 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         14 my ($ok, $err) = &try($sub);
121 4         49 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       276 croak "Not currently building a mock"
129             unless @BUILD;
130              
131 5         10 my $build = $BUILD[-1];
132              
133 5 100 66     252 croak "'$meth' is not a valid action for mock_do()"
134             if $meth =~ m/^_/ || !$build->can($meth);
135              
136 3         13 $build->$meth(@args);
137             }
138              
139             sub mock_obj {
140 6     6 1 474 my ($proto) = @_;
141              
142 6 100 66     46 if ($proto && ref($proto) && reftype($proto) ne 'CODE') {
      100        
143 4         9 shift @_;
144             }
145             else {
146 2         4 $proto = {};
147             }
148              
149 6         16 my $class = _generate_class();
150 6         8 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   17 my $c = mock_building;
158              
159             # We want to do these BEFORE anything that the sub may do.
160 1         6 $c->block_load(1);
161 1         11 $c->purge_on_destroy(1);
162 1         10 $c->autoload(1);
163              
164 1         17 $orig->(@_);
165             },
166 1         6 );
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         32 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         70 $new->{'~~MOCK~CONTROL~~'} = $control;
184 6         32 return $new;
185             }
186              
187             sub _generate_class {
188 6     6   10 my $prefix = __PACKAGE__;
189              
190 6         16 for (1 .. 100) {
191 6         17 my $postfix = join '', map { chr(rand(26) + 65) } 1 .. 32;
  192         343  
192 6         28 my $class = $prefix . '::__TEMP__::' . $postfix;
193 6         11 my $file = $class;
194 6         33 $file =~ s{::}{/}g;
195 6         16 $file .= '.pm';
196 6 50       21 next if $INC{$file};
197 155     155   1405 my $stash = do { no strict 'refs'; \%{"${class}\::"} };
  155         648  
  155         118603  
  6         10  
  6         7  
  6         74  
198 6 50       20 next if keys %$stash;
199 6         18 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 340 my $proto = shift;
207 33   66     169 my $class = blessed($proto) || $proto;
208 33         96 my @args = @_;
209              
210 33         69 my $void = !defined(wantarray);
211              
212             my $callback = sub {
213 47     47   901 my ($parent) = reverse mocked($class);
214 47         161 my $control;
215              
216 47 100 66     325 if (@args == 1 && ref($args[0]) && reftype($args[0]) eq 'CODE') {
      66        
217 1         5 $control = Test2::Mock->new(class => $class);
218 1         4 mock_build($control, @args);
219             }
220             else {
221 46         523 $control = Test2::Mock->new(class => $class, @args);
222             }
223              
224 47 100       138 if ($parent) {
225 3         5 $control->{parent} = $parent;
226 3         11 weaken($parent->{child} = $control);
227             }
228              
229 47   100     296 $MOCKS{$class} ||= [];
230 47         82 push @{$MOCKS{$class}} => $control;
  47         154  
231 47         231 weaken($MOCKS{$class}->[-1]);
232              
233 47         372 return $control;
234 33         216 };
235              
236 33 100       100 return $callback->() unless $void;
237              
238 13         19 my $level = 0;
239 13         23 my $caller;
240 13         152 while (my @call = caller($level++)) {
241 26 100       138 next if $call[0] eq __PACKAGE__;
242 13         33 $caller = \@call;
243 13         46 last;
244             }
245              
246 13         35 my $handled;
247 13         41 for my $handler (@{$HANDLERS{$caller->[0]}}) {
  13         78  
248 13 50       61 $handled++ if $handler->(
249             class => $class,
250             caller => $caller,
251             builder => $callback,
252             args => \@args,
253             );
254             }
255              
256 13 50       124 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 276 return map {( $_ => gen_accessor($_) )} @_;
  3         10  
262             }
263              
264             sub mock_accessor {
265 2     2 1 21 my ($field) = @_;
266 2         8 return gen_accessor($field);
267             }
268              
269             sub mock_getters {
270 1     1 1 10 my ($prefix, @list) = @_;
271 1         3 return map {( "$prefix$_" => gen_reader($_) )} @list;
  3         8  
272             }
273              
274             sub mock_getter {
275 1     1 1 19 my ($field) = @_;
276 1         3 return gen_reader($field);
277             }
278              
279             sub mock_setters {
280 1     1 1 6 my ($prefix, @list) = @_;
281 1         3 return map {( "$prefix$_" => gen_writer($_) )} @list;
  3         10  
282             }
283              
284             sub mock_setter {
285 1     1 1 25 my ($field) = @_;
286 1         4 return gen_writer($field);
287             }
288              
289             1;
290              
291             __END__