File Coverage

blib/lib/Test/DeepMock.pm
Criterion Covered Total %
statement 32 75 42.6
branch 0 34 0.0
condition 0 9 0.0
subroutine 9 13 69.2
pod n/a
total 41 131 31.3


line stmt bran cond sub pod time code
1             package Test::DeepMock;
2              
3 1     1   13186 use 5.008;
  1         2  
4 1     1   3 use strict;
  1         2  
  1         35  
5 1     1   4 use warnings;
  1         8  
  1         74  
6              
7             =head1 NAME
8              
9             Test::DeepMock - Awesome abstract class to mock everything for unit tests.
10              
11             Test::DeepMock unshifts objects into @INC which handle "require" and returns what you need
12             for your tests.
13              
14             =head1 VERSION
15              
16             Version 0.0.1
17              
18             =cut
19              
20             our $VERSION = '0.0.1';
21              
22              
23             =head1 SYNOPSIS
24              
25             Test::DeepMock is a abstract mock factory which injects mocks in @INC so
26             whenever your app "requires" the package the mock will be loaded.
27             Extreemly usefull for testing old legacy code.
28              
29             Create your factory of mocks:
30              
31             package My::Factory;
32             use Test::DeepMock ();
33              
34             our @ISA = qw( Test::DeepMock );
35             our @CONFIG = {
36             'My::Package' => {
37             source => 'package My::Package; 1;'
38             },
39             'My::Another::Package' => {
40             file_handle => $FH
41             },
42             'My::Package::From::File' => {
43             path => '/some/path/to/mock'
44             },
45             default => sub {
46             my ($class, $package_name) = @_;
47             #returns scalar with source of package or file handle
48             #return undef to interrupt mocking
49             },
50             };
51              
52             In the test: import packages that you want to mock from your factory:
53              
54             use My::Factory qw(
55             My::Package
56             My::Another::Package
57             My::Package::From::File
58             This::Package::Will::Triger::Default::Subroutine
59             );
60              
61             =head1 CONFIG
62              
63             our $CONFIG in the ancestor of Test::DeepMock will identify a mock configuration.
64              
65             =head2 keys of config
66              
67             Keys are the package names (e.g. 'My::Package'); Also, there are 'default' entry, which should provide
68             subroutine which returns scalar (package content) or file handle to a file with package content.
69              
70              
71             =head1 Mock configuration
72              
73             Each mock package in our $CONFIG can have following keys:
74              
75             =head2 source
76              
77             Scalar or scalar reference with package content, which will be loaded.
78              
79             =head2 file_handle
80              
81             File handle to a file with package content
82              
83             =head2 path
84              
85             Path to a foulder where to look for a package (similar to @INC or PERL5LIB entries)
86              
87              
88              
89             =head1 SUBROUTINES/METHODS
90              
91             =head2 import
92              
93             "import" is used to specify which packages you want to mock. Does not actualy import something.
94              
95             =cut
96              
97             our $CONFIG;
98             our $PATH_TO_MOCKS;
99              
100             sub import {
101 1     1   9 my ($class, @packages) = @_;
102              
103 1         2 my ($config, $path_to_mocks);
104             {
105 1     1   3 no strict 'refs';
  1         1  
  1         451  
  1         1  
106 1         1 $config = ${$class."::CONFIG"};
  1         3  
107 1         1 $path_to_mocks = ${$class."::PATH_TO_MOCKS"};
  1         2  
108             }
109              
110 1         1 my $default_handler = $config->{default};
111              
112 1         8 foreach my $package (@packages) {
113 0           my $file_name = $package;
114 0           $file_name =~ s/::/\//g;
115 0           $file_name .= '.pm';
116 0           my %inc_args = (
117             file_name => $file_name
118             );
119 0           my $package_config = $config->{$package};
120              
121             #Searching for mocks:
122             # 1. by source
123             # 2. by file_handle
124             # 3. by path
125             # 4. default handler
126 0 0 0       if (defined $package_config->{source}){
    0          
    0          
    0          
127             # by source
128 0           $inc_args{source} = $package_config->{source};
129             } elsif (defined $package_config->{file_handle}){
130             # by file_handle
131 0           $inc_args{file_handle} = $package_config->{file_handle};
132             } elsif (defined $package_config->{path} || defined $path_to_mocks){
133             # lets try to pick up mocks from file system
134 0 0         my $mock_path = defined $package_config->{path} ? $package_config->{path} : $path_to_mocks;
135 0           my $FH;
136 0 0         open($FH, "< $mock_path/$inc_args{file_name}") or undef $FH;
137              
138 0 0         warn "cannot open file: $mock_path/$inc_args{file_name}"
139             unless $FH;
140              
141 0 0         if ($FH) {
    0          
142 0           $inc_args{file_handle} = $FH;
143             } elsif (ref $default_handler eq 'CODE') {
144             # this is odd, but lets try to run default handler
145 0           warn "Running default handler for $package";
146 0           _run_default_handler($class, $default_handler, $package, \%inc_args);
147             } else {
148             # ok, I give up
149 0           die "could not mock $package. Please, check \$$class\::CONFIG";
150             }
151             } elsif (ref $default_handler eq 'CODE'){
152 0           _run_default_handler($class, $default_handler, $package, \%inc_args);
153             } else {
154             # I give up
155 0           die "could not mock $package. Please, check \$$class\::CONFIG";
156             }
157              
158 0           _fix_up_inc_args(\%inc_args);
159              
160 0           my $interceptor = Test::Util::Inc->new(
161             %inc_args,
162             );
163 0           unshift @INC, $interceptor;
164             }
165             }
166              
167             sub _run_default_handler {
168 0     0     my ($class, $handler, $package, $inc_args) = @_;
169 0           my $mock = eval {&$handler($class, $package)};
  0            
170 0 0         if ($@) {
171 0           die "default handler died for '$package' with $@";
172             }
173 0 0         die "could not mock $package. Please, check \$$class\::CONFIG"
174             unless $mock;
175              
176 0 0 0       my $type = (ref $mock eq "GLOB") ? "file_handle" :
    0          
177             (!ref $mock || ref $mock eq 'SCALAR') ? "source" : undef;
178 0 0         die "could not mock $package. Default handler returned neither scalar nor file handle. Please, check \$$class\::CONFIG"
179             unless $type;
180              
181 0           $inc_args->{$type} = $mock;
182             }
183              
184             sub _fix_up_inc_args {
185 0     0     my $inc_args = shift;
186 0           my $source = $inc_args->{source};
187 0 0 0       $inc_args->{source} = \$source
188             if defined $source && !ref $source;
189             }
190              
191             =head1 AUTHOR
192              
193             Mykhailo Koretskyi, C<< >>
194              
195             =head1 BUGS
196              
197             Please report any bugs or feature requests to C, or through
198             the web interface at L. I will be notified, and then you'll
199             automatically be notified of progress on your bug as I make changes.
200              
201              
202              
203              
204             =head1 SUPPORT
205              
206             You can find documentation for this module with the perldoc command.
207              
208             perldoc Test::DeepMock
209              
210              
211             You can also look for information at:
212              
213             =over 4
214              
215             =item * RT: CPAN's request tracker (report bugs here)
216              
217             L
218              
219             =item * AnnoCPAN: Annotated CPAN documentation
220              
221             L
222              
223             =item * CPAN Ratings
224              
225             L
226              
227             =item * Search CPAN
228              
229             L
230              
231             =back
232              
233              
234             =head1 ACKNOWLEDGEMENTS
235              
236              
237             =head1 LICENSE AND COPYRIGHT
238              
239             Copyright 2017 Mykhailo Koretskyi.
240              
241             This program is free software; you can redistribute it and/or modify it
242             under the terms of the the Artistic License (2.0). You may obtain a
243             copy of the full license at:
244              
245             L
246              
247             Any use, modification, and distribution of the Standard or Modified
248             Versions is governed by this Artistic License. By using, modifying or
249             distributing the Package, you accept this license. Do not use, modify,
250             or distribute the Package, if you do not accept this license.
251              
252             If your Modified Version has been derived from a Modified Version made
253             by someone other than you, you are nevertheless required to ensure that
254             your Modified Version complies with the requirements of this license.
255              
256             This license does not grant you the right to use any trademark, service
257             mark, tradename, or logo of the Copyright Holder.
258              
259             This license includes the non-exclusive, worldwide, free-of-charge
260             patent license to make, have made, use, offer to sell, sell, import and
261             otherwise transfer the Package with respect to any patent claims
262             licensable by the Copyright Holder that are necessarily infringed by the
263             Package. If you institute patent litigation (including a cross-claim or
264             counterclaim) against any party alleging that the Package constitutes
265             direct or contributory patent infringement, then this Artistic License
266             to you shall terminate on the date that such litigation is filed.
267              
268             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
269             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
270             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
271             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
272             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
273             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
274             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
275             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
276              
277              
278             =cut
279              
280             1; # End of Test::DeepMock
281              
282             package Test::Util::Inc;
283 1     1   4 use strict;
  1         1  
  1         15  
284 1     1   2 use warnings;
  1         2  
  1         29  
285              
286 1     1   3 use constant MOCK_PATH => $ENV{DEEP_MOCK_PATH};
  1         2  
  1         72  
287 1         120 use constant OUTPUT_KEYS => [
288             qw ( source file_handle file_name)
289 1     1   3 ];
  1         1  
290              
291             sub new {
292 0     0     my ($class, %args) = @_;
293              
294             my $self = {
295 0           (map { ($_ => $args{$_}) } @{OUTPUT_KEYS()}),
  0            
  0            
296             };
297              
298 0           bless $self, $class;
299              
300 0           return $self;
301             }
302              
303             sub Test::Util::Inc::INC {
304 0     0     my ($self, $filename) = @_;
305              
306             # skip, if filename doesn`t match
307             return undef
308 0 0         if $self->{file_name} ne $filename;
309              
310 0 0         return $self->{source} ? $self->{source} : $self->{file_handle};
311             }
312              
313             1;
314