Skip to content

Commit 3ba8bc7

Browse files
DrHydegeofffranks
authored andcommitted
lexically scoped strictness
1 parent e57fc5a commit 3ba8bc7

File tree

8 files changed

+166
-13
lines changed

8 files changed

+166
-13
lines changed

Changes

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
Revision history for Test::MockModule
22

3+
vX.XXX.X
4+
- XXXXXXX 'strict' mode is now lexically scoped
5+
36
v0.175.0
47
- 964aa2a Ignore CI files and whitesource - Nicolas R
58

lib/Test/MockModule.pm

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,35 @@ use Carp;
77
use SUPER;
88
$VERSION = '0.175.0';
99

10-
our $STRICT_MODE;
11-
1210
sub import {
1311
my ( $class, @args ) = @_;
1412

13+
# default if no args
14+
$^H{'Test::MockModule/STRICT_MODE'} = 0;
15+
1516
foreach my $arg (@args) {
1617
if ( $arg eq 'strict' ) {
17-
$STRICT_MODE = 1;
18-
}
19-
else {
18+
$^H{'Test::MockModule/STRICT_MODE'} = 1;
19+
} elsif ( $arg eq 'nostrict' ) {
20+
$^H{'Test::MockModule/STRICT_MODE'} = 0;
21+
} else {
2022
warn "Test::MockModule unknown import option '$arg'";
2123
}
2224
}
23-
2425
return;
2526
}
27+
28+
sub _strict_mode {
29+
my $depth = 0;
30+
while(my @fields = caller($depth++)) {
31+
my $hints = $fields[10];
32+
if($hints && grep { /^Test::MockModule\// } keys %{$hints}) {
33+
return $hints->{'Test::MockModule/STRICT_MODE'};
34+
}
35+
}
36+
return 0;
37+
}
38+
2639
my %mocked;
2740
sub new {
2841
my $class = shift;
@@ -102,7 +115,7 @@ sub define {
102115
sub mock {
103116
my ($self, @mocks) = (shift, @_);
104117

105-
croak "mock is not allowed in strict mode. Please use define or redefine" if $STRICT_MODE;
118+
croak "mock is not allowed in strict mode. Please use define or redefine" if($self->_strict_mode());
106119

107120
return $self->_mock(@mocks);
108121
}
@@ -140,7 +153,7 @@ sub _mock {
140153
sub noop {
141154
my $self = shift;
142155

143-
croak "noop is not allowed in strict mode. Please use define or redefine" if $STRICT_MODE;
156+
croak "noop is not allowed in strict mode. Please use define or redefine" if($self->_strict_mode());
144157

145158
$self->_mock($_,1) for @_;
146159

@@ -287,7 +300,7 @@ Test::MockModule - Override subroutines in a module for unit testing
287300
}
288301
289302
# If you want to prevent noop and mock from working, you can
290-
# load Test::MockModule in strict mode
303+
# load Test::MockModule in strict mode.
291304
292305
use Test::MockModule qw/strict/;
293306
my $module = Test::MockModule->new('Module::Name');
@@ -298,6 +311,16 @@ Test::MockModule - Override subroutines in a module for unit testing
298311
# Dies since you specified you wanted strict mode.
299312
$module->mock('subroutine', sub { ... });
300313
314+
# Turn strictness off in this lexical scope
315+
{
316+
use Test::MockModule 'nostrict';
317+
# ->mock() works now
318+
$module->mock('subroutine', sub { ... });
319+
}
320+
321+
# Back in the strict scope, so mock() dies here
322+
$module->mock('subroutine', sub { ... });
323+
301324
=head1 DESCRIPTION
302325
303326
C<Test::MockModule> lets you temporarily redefine subroutines in other packages
@@ -308,6 +331,53 @@ module. The object remembers the original subroutine so it can be easily
308331
restored. This happens automatically when all MockModule objects for the given
309332
module go out of scope, or when you C<unmock()> the subroutine.
310333
334+
=head1 STRICT MODE
335+
336+
One of the weaknesses of testing using mocks is that the implementation of the
337+
interface that you are mocking might change, while your mocks get left alone.
338+
You are not now mocking what you thought you were, and your mocks might now be
339+
hiding bugs that will only be spotted in production. To help prevent this you
340+
can load Test::MockModule in 'strict' mode:
341+
342+
use Test::MockModule qw(strict);
343+
344+
This will disable use of the C<mock()> method, making it a fatal runtime error.
345+
You should instead define mocks using C<redefine()>, which will only mock
346+
things that already exist and die if you try to redefine something that doesn't
347+
exist.
348+
349+
Strictness is lexically scoped, so you can do this in one file:
350+
351+
use Test::MockModule qw(strict);
352+
353+
...->redefine(...);
354+
355+
and this in another:
356+
357+
use Test::MockModule; # the default is nostrict
358+
359+
...->mock(...);
360+
361+
You can even mix n match at different places in a single file thus:
362+
363+
use Test::MockModule qw(strict);
364+
# here mock() dies
365+
366+
{
367+
use Test::MockModule qw(nostrict);
368+
# here mock() works
369+
}
370+
371+
# here mock() goes back to dieing
372+
373+
use Test::MockModule qw(nostrict);
374+
# and from here on mock() works again
375+
376+
NB that strictness must be defined at compile-time, and set using C<use>. If
377+
you think you're going to try and be clever by calling Test::MockModule's
378+
C<import()> method at runtime then what happens in undefined, with results
379+
differing from one version of perl to another. What larks!
380+
311381
=head1 METHODS
312382
313383
=over 4
@@ -538,6 +608,8 @@ Current Maintainer: Geoff Franks <[email protected]>
538608
539609
Original Author: Simon Flack E<lt>simonflk _AT_ cpan.orgE<gt>
540610
611+
Lexical scoping of strictness: David Cantrell E<lt>[email protected]E<gt>
612+
541613
=head1 COPYRIGHT
542614
543615
Copyright 2004 Simon Flack E<lt>simonflk _AT_ cpan.orgE<gt>.

t/lib/ScopedStrict/Mockee1.pm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package ScopedStrict::Mockee1;
2+
3+
use strict;
4+
use warnings;
5+
6+
sub gonna_mock_this { return "you're not going to see this" }
7+
8+
1;

t/lib/ScopedStrict/Mockee2.pm

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package ScopedStrict::Mockee2;
2+
3+
use strict;
4+
use warnings;
5+
6+
sub also_gonna_mock_this { return "you're not going to see this either" }
7+
8+
1;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package ScopedStrict::NonStrictMocker;
2+
3+
use strict;
4+
use warnings;
5+
6+
use Test::MockModule;
7+
8+
Test::MockModule->new('ScopedStrict::Mockee2')->mock(
9+
also_gonna_mock_this => sub { "another mocked sub" }
10+
);
11+
12+
1;

t/lib/ScopedStrict/StrictMocker.pm

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package ScopedStrict::StrictMocker;
2+
3+
use strict;
4+
use warnings;
5+
6+
use Test::MockModule qw(strict);
7+
8+
Test::MockModule->new('ScopedStrict::Mockee1')->redefine(
9+
gonna_mock_this => sub { "mocked sub" }
10+
);
11+
12+
1;

t/mock_strict.t

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use Test::MockModule qw/strict/;
88

99
my $mocker = Test::MockModule->new('Mockee');
1010

11-
is( $Test::MockModule::STRICT_MODE, 1, "use Test::MockModule qw/strict/; sets \$STRICT_MODE to 1" );
11+
is( Test::MockModule->_strict_mode(), 1, "use Test::MockModule qw/strict/; sets strict mode" );
1212

1313
eval { $mocker->mock( 'foo', 2 ) };
1414
like( "$@", qr/^mock is not allowed in strict mode. Please use define or redefine at/, "mock croaks in strict mode." );
@@ -22,9 +22,27 @@ is( Mockee->foo, "abc", "define is allowed in strict mode." );
2222
$mocker->redefine( 'existing_subroutine', "def" );
2323
is( Mockee->existing_subroutine, "def", "redefine is allowed in strict mode." );
2424

25-
$Test::MockModule::STRICT_MODE = 0;
26-
$mocker->mock( 'foo', 123 );
27-
is( Mockee->foo, 123, "mock is allowed when strict mode is turned off." );
25+
{
26+
use Test::MockModule 'nostrict'; # no strictness in this lexical scope
27+
is( Test::MockModule->_strict_mode(), 0, "nostrict turns strictness off");
28+
$mocker->mock( 'foo', 123 );
29+
is( Mockee->foo, 123, "mock is allowed when strict mode is turned off." );
30+
{
31+
use Test::MockModule 'strict'; # but we are strict here again
32+
eval { $mocker->mock( 'foo', 2 ) };
33+
like( "$@", qr/^mock is not allowed in strict mode/,
34+
"we can nest alternating strict/nostrict soooo deeply");
35+
}
36+
$mocker->mock('foo', 456);
37+
pass("Back in a non-strict scope, the intervening strict scope didn't make ->mock() crash");
38+
}
39+
40+
eval { $mocker->mock( 'foo', 2 ) };
41+
like( "$@", qr/^mock is not allowed in strict mode. Please use define or redefine at/, "Finally, back in the original scope, and we return to being strict");
42+
43+
use Test::MockModule 'nostrict'; # same lexical scope as we opened in, but change how strict it is
44+
$mocker->mock('foo', 94);
45+
pass("Changed to nostrict in a previously strict scope, mock() didn't crash");
2846

2947
done_testing();
3048

t/strict_scoped_files.t

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
use strict;
2+
use warnings;
3+
4+
use Test::More;
5+
6+
use lib 't/lib';
7+
8+
# things we're going to mock
9+
use ScopedStrict::Mockee1;
10+
use ScopedStrict::Mockee2;
11+
12+
# mock one of them in strict mode
13+
use ScopedStrict::StrictMocker;
14+
# this doesn't turn on strict mode, and tries to use ->mock(). It
15+
# shouldn't crash
16+
use ScopedStrict::NonStrictMocker;
17+
18+
# yay, we didn't crash!
19+
pass "Using 'strict' mode in one module that we use didn't prevent ->mock()ing in another";
20+
done_testing();

0 commit comments

Comments
 (0)