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