File Coverage

blib/lib/SimpleMock.pm
Criterion Covered Total %
statement 91 92 98.9
branch 23 26 88.4
condition 4 5 80.0
subroutine 18 18 100.0
pod 3 3 100.0
total 139 144 96.5


line stmt bran cond sub pod time code
1             package SimpleMock;
2 8     8   1153785 use strict;
  8         15  
  8         230  
3 8     8   27 use warnings;
  8         10  
  8         286  
4 8     8   2981 use SimpleMock::ScopeGuard;
  8         18  
  8         204  
5 8     8   30 use Exporter qw(import);
  8         14  
  8         175  
6 8     8   3540 use Hash::Merge qw(merge);
  8         48669  
  8         428  
7 8     8   41 use Carp qw(carp);
  8         9  
  8         303  
8              
9 8         2442 use SimpleMock::Util qw(
10             all_file_subs
11             generate_args_sha
12             namespace_from_file
13 8     8   2728 );
  8         17  
14              
15             our @EXPORT_OK = qw(
16             register_mocks
17             register_mocks_scoped
18             clear_mocks
19             );
20              
21             our $VERSION = '0.04';
22              
23             # mocks get stored in a stack, with globals as the first element
24             our @MOCK_STACK = ( {} );
25              
26             # enable this env var to troubleshoot
27             sub _debug {
28 483     483   655 my $message = shift;
29 483 50       1149 $ENV{DEBUG_SIMPLEMOCK} and carp "DEBUG: $message";
30             }
31              
32             # "globals"
33             sub register_mocks {
34 34     34 1 25775 my %mocks_data = @_;
35 34         107 _register_into_layer($MOCK_STACK[0], \%mocks_data);
36             }
37              
38             # pushes a new layer, registers into it, returns a guard
39             sub register_mocks_scoped {
40 7     7 1 5475 my %mocks_data = @_;
41 7         17 my $layer = {};
42 7         34 push @MOCK_STACK, $layer;
43 7         27 _register_into_layer($layer, \%mocks_data);
44 7         726 return SimpleMock::ScopeGuard->new($layer);
45             }
46              
47             sub _register_into_layer {
48 45     45   77 my ($layer, $mocks_data) = @_;
49 45         128 foreach my $model (keys %$mocks_data) {
50 46 100       564 $model =~ /^[A-Z_]+$/ or die "Mock model class must be ALL_CAPS and underscores only! ($model)";
51 45         83 my $model_ns = "SimpleMock::Model::$model";
52              
53             # load model NS if needed — convert Foo::Bar to Foo/Bar.pm for block eval
54 45         199 (my $model_file = $model_ns) =~ s{::}{/}g;
55 45         73 $model_file .= '.pm';
56 45 50       60 eval { require $model_file }; die $@ if $@;
  45         5631  
  45         101  
57 8     8   42 no strict 'refs'; ## no critic
  8         12  
  8         2542  
58 45         251 my $processed = "${model_ns}::validate_mocks"->($mocks_data->{$model});
59              
60             # merge INTO the layer reference in-place
61 43         57 %$layer = %{ Hash::Merge::merge($processed, $layer) };
  43         147  
62             }
63             }
64              
65             sub _register_into_current_scope {
66 4     4   14 my %mocks_data = @_;
67 4         9 _register_into_layer($MOCK_STACK[-1], \%mocks_data);
68             }
69              
70             sub _load_mocks_for {
71 448     448   639 my $original_filename = shift;
72 448         1209 _debug("_load_mocks_for($original_filename)");
73             # Skip if the file is a SimpleMock file
74 448 100       960 return if $original_filename =~ /^SimpleMock\b/;
75              
76 424         532 my $mock_filename = "SimpleMock/Mocks/$original_filename";
77 424         463 eval {
78 424         49877 require $mock_filename;
79             };
80 424 100       1794 if ($@) {
81             # mock doesn't exist — match only the file-not-found form so that
82             # "Can't locate object method" dies inside the mock file still surface
83 407 50       2145 $@ =~ /\ACan't locate \S+ in \@INC/ and return;
84             # mock is borked
85 0         0 die "Error loading $mock_filename: $@";
86             }
87 17         67 _debug("Loaded mocks for $original_filename ($mock_filename)");
88              
89             # map any method that exists in the mock over to the original
90             # as a default mock
91 17         70 my @module_subs = all_file_subs($original_filename);
92 17         46 my $mock_ns = namespace_from_file($mock_filename);
93 17         33 my $original_ns = namespace_from_file($original_filename);
94              
95 17         71 my $default_sub_mocks;
96 17         34 foreach my $sub_name (@module_subs) {
97 891         854 $sub_name =~ s/.*:://;
98 891         799 my $mock_sub = $mock_ns.'::'.$sub_name;
99 8     8   43 no strict 'refs'; ## no critic
  8         9  
  8         2845  
100 891 100       688 if (defined &{$mock_sub}) {
  891         1954  
101 3         10 _debug("Mapping mock sub $mock_sub to original sub ${original_ns}::$sub_name");
102 3         5 $default_sub_mocks->{$original_ns}->{$sub_name} = [ { returns => \&{$mock_sub} } ];
  3         13  
103             }
104             }
105 17 100       72 register_mocks(SUBS => $default_sub_mocks) if $default_sub_mocks;
106             }
107              
108             sub clear_mocks {
109 3     3 1 2024 my @classes = @_;
110 3 100       8 if (@classes) {
111 1         11 delete $MOCK_STACK[0]->{$_} for (@classes);
112             }
113             else {
114 2         3 %{$MOCK_STACK[0]} = ();
  2         22  
115             }
116             }
117              
118             # override "require" to trigger loading of mocks
119             BEGIN {
120 8     8   22 our %processed;
121             *CORE::GLOBAL::require = sub {
122 2346     2346   1169644 my $filename = shift;
123              
124             # special cases (not module loads)
125 2346 100 66     13302 return CORE::require($filename)
126             if ($filename !~ /[A-Za-z]/ || $filename =~ /\.pl$/);
127              
128             # if namespace, switch to file name
129 2253 100       5138 unless ($filename =~ /\.pm$/) {
130 1         3 $filename =~ s|::|/|g;
131 1         5 $filename .= '.pm';
132             }
133              
134             # only load if not already processed
135 2253 100       4169 unless ($processed{$filename}) {
136 448         799 $processed{$filename}=1;
137 448         520 eval { CORE::require($filename) };
  448         134391  
138 448 100       300550 $@ and _debug("Can't require file $filename: $@");
139 448         917 _load_mocks_for($filename);
140             }
141 2253   100     7759 return $INC{$filename} || 1;
142 8         492 };
143             }
144              
145             1;
146              
147             =head1 NAME
148              
149             SimpleMock - A simple mocking framework for Perl
150              
151             =head1 SYNOPSIS
152              
153             use SimpleMock qw(register_mocks);
154              
155             # register mocks for a model
156             register_mocks(
157             SUBS => {
158             'MyModule' => {
159             'my_method' => [
160             { returns => sub { return 42 } },
161             ],
162             },
163             },
164             DBI => {
165             QUERIES => [
166             ...
167             ],
168             },
169             LWP_UA => {
170             ...
171             },
172             );
173              
174             {
175             my $scope_guard = register_mocks_scoped(...);
176             # tests here will use the scoped mocks
177             }
178             # scoped mocks are destroyed and previously overwritten mocks are restored
179              
180             =head1 DESCRIPTION
181              
182             SimpleMock is a simple, extendable mocking framework for Perl. The
183             following models are supported by default:
184              
185             =over 4
186              
187             =item * SUBS - for mocking subroutine calls
188              
189             =item * DBI - for mocking DBI code
190              
191             =item * LWP_UA - for mocking LWP::UserAgent code
192              
193             =item * PATH_TINY - for mocking Path::Tiny code
194              
195             =back
196              
197             See documentation in each SimpleMock::Model::* namespace for details of
198             the mock data formats.
199              
200             Other models can easily be added via the SimpleMock::Model namespace. If
201             you add mocks that are for a commonly used module, please consider submitting
202             a pull request so that others can use them.
203              
204             Currently, there is no versioning of the mocks, so you should
205             ensure that the mocks you use are compatible with the version of the
206             module you are mocking. If there is a good reason to version the mocks,
207             I have architected it, but not implemented. I am happy to add it but
208             have yet to hit a use case in production code to justify it.
209              
210             =head2 DEFINING MOCKS
211              
212             Mocks can be defined via:
213              
214             =over 4
215              
216             =item * defined sub in SimpleMock::Mocks modules
217              
218             =item * calls to register_mocks in SimpleMock::Mocks modules
219              
220             =item * calls to register_mocks in your test code
221              
222             =item * calls to register_mocks_scoped in blocks in your tests
223              
224             =back
225              
226             =head1 GETTING STARTED
227              
228             Look at each of the model modules to see how to define each mock type:
229              
230             perldoc SimpleMock::Model::SUBS
231             perldoc SimpleMock::Model::DBI
232             perldoc SimpleMock::Model::LWP_UA
233             perldoc SimpleMock::Model::PATH_TINY
234              
235             The SUBS model also goes over the various ways you can define mocks.
236              
237             In calls to C and C the arg sent
238             is a hash where the keys are the model we are mocking, ie:
239              
240             register_mocks(
241             SUBS => { ... },
242             DBI => { ... },
243             LWP_UA => { ... },
244             PATH_TINY => { ... },
245             );
246              
247             =head1 ARCHITECTURE
248              
249             +------------------+
250             | Test Code |
251             +------------------+
252             |
253             | register_mocks( MODEL => { ... } )
254             | register_mocks_scoped( MODEL => { ... } )
255             | clear_mocks()
256             v
257             +------------------+ +-------------------------------------------+
258             | SimpleMock.pm |---->| @MOCK_STACK |
259             | | | +---------------------------------------+ |
260             | require override| | | Layer 2 (inner scope) <- searched 1st| |
261             | _load_mocks_for | | +---------------------------------------+ |
262             | | | | Layer 1 (outer scope) <- searched 2nd| |
263             +------------------+ | +---------------------------------------+ |
264             | | Layer 0 (global base) <- searched 3rd| |
265             | +---------------------------------------+ |
266             +-------------------------------------------+
267             |
268             +-----------------------------+--------------------+
269             | | | |
270             v v v v
271             +-----------+ +-----------+ +-------------+ +-------------+
272             |Model::SUBS| |Model::DBI | |Model::LWP_UA| |Model::PATH_ |
273             | | | | | | | TINY |
274             |validate_ | |validate_ | |validate_ | |validate_ |
275             | mocks() | | mocks() | | mocks() | | mocks() |
276             +-----------+ +-----------+ +-------------+ +-------------+
277             | | | |
278             v v v v
279             +-----------+ +-----------+ +------------+ +-------------+
280             |Mocks::* | |Mocks::DBI | |Mocks::LWP::| |Mocks::Path::|
281             |(auto-load)| | | | UserAgent | | Tiny |
282             | | |overrides | |overrides | |overrides |
283             |delegation | |DBI:: | |LWP::User | |Path::Tiny |
284             |wrappers | | connect | | Agent::new | | methods |
285             +-----------+ +-----------+ +------------+ +-------------+
286             |
287             v
288             +-----------+
289             |DBD:: |
290             |SimpleMock |
291             | (driver) |
292             +-----------+
293              
294             Flow:
295             use SimpleMock -> installs CORE::GLOBAL::require override
296             use MyModule -> override loads SimpleMock::Mocks::MyModule
297             (if it exists), auto-registers matching subs
298             register_mocks -> Model::*::validate_mocks() normalises data,
299             merges into Layer 0
300             scoped mocks -> push new layer, ScopeGuard::DESTROY pops it
301             mock lookup -> traverse stack top-to-bottom, first match wins
302              
303             =head1 METHODS
304              
305             =head2 register_mocks
306              
307             It takes a hash of model mocks where the top level keys refer to the model
308             namespace under SimpleMock::Model and the values define the actual mocks.
309             Different mocks can have different formats - eg, SUBS have namespaces with
310             methods, DBI has a hash of queries and a hash of meta flags.
311              
312             use SimpleMock qw(register_mocks);
313              
314             register_mocks(
315             SUBS => {
316             'MyModel' => {
317             'my_method' => [
318             { returns => sub { return 42 } },
319             ],
320             },
321             },
322             DBI => {
323             QUERIES => [
324             {
325             sql => 'SELECT name, email FROM user where name like=?',
326             results => [
327             # data is an arrayref of arrayrefs of results
328             { args => [ 'C%' ], data => $d1 },
329             # if you set a result with no args, it will be used as the default
330             { data => $d2 },
331             ],
332             },
333             {
334             sql => 'SELECT id, name, email FROM member WHERE name like=?',
335             # cols is only needed if using selectall_hashref etc
336             cols => [ 'id', 'name', 'email' ],
337             results => [
338             { args => [ 'C%' ], data => $d3 },
339             { args => [ 'D%' ], data => $d4 },
340             ],
341             },
342             ],
343             },
344             );
345              
346             =head2 register_mocks_scoped
347              
348             As above, but the mocks will go out of scope at the end of the current block.
349              
350             A scope guard is returned, and a DESTROY block on this object removes the scoped
351             mocks as appropriate.
352              
353             Note: the underlying original method is not restored when a scoped mock is
354             deleted. The use case would be to temporarily change a global mock just for a specific
355             test without having to put it in a separate file. No error is explicitly thrown for
356             scoped mocks that do not have an underlying global mock.
357              
358             use SimpleMock qw(register_mocks register_mocks_scoped);
359              
360             # set global mocks
361             register_mocks(...);
362              
363             {
364             # set scoped mocks
365             my $scope_guard = register_mocks_scoped(...);
366             # scope registered mocks are available
367             }
368             # scope registered mocks are no longer available
369              
370             =head2 clear_mocks
371              
372             Clears registered mocks from the base layer. Pass one or more model names to
373             clear only those models, or call with no arguments to clear everything.
374              
375             use SimpleMock qw(clear_mocks);
376              
377             clear_mocks('DBI'); # clear only DBI mocks
378             clear_mocks('DBI', 'LWP_UA'); # clear DBI and LWP_UA mocks
379             clear_mocks(); # clear all mocks
380              
381             Note: clearing mocks does not restore the original subroutine implementations
382             for SUBS mocks. The delegation wrappers remain in place, and calling a cleared
383             mock sub will die with "No mock found".
384              
385             =head1 BUGS AND LIMITATIONS
386              
387             I have a feeling this doesn't work well with some XS modules, nor with any modules that
388             override the CORE require, but I haven't tested heavily in that direction.
389              
390             =head1 REPOSITORY
391              
392             L
393              
394             Pull requests to add common modules to the framework are welcomed!
395              
396             =head1 AUTHOR
397              
398             Clive Holloway
399              
400             =head1 COPYRIGHT AND LICENSE
401              
402             This software is copyright (c) 2025-2026 by Clive Holloway.
403             This is free software; you can redistribute it and/or modify it under
404             the same terms as the Perl 5 programming language system itself.
405