File Coverage

blib/lib/Test/DeepMock.pm
Criterion Covered Total %
statement 75 78 96.1
branch 26 34 76.4
condition 8 11 72.7
subroutine 14 14 100.0
pod n/a
total 123 137 89.7


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