Mock modules
- Test2::V0 - I really must start using this soon. See Test2::Mock and possibly Test2::Tools::Mock
- Test::MockObject - Works. Has set_isa() to pass Moose constraints. Start with an empty object and add methods as needed. This one.
- see also Test::MockObject::Extends
- Test::MockModule - "Override subroutines in a module for unit testing".
- Does not fool Moose (modules appear as Test::MockModule instead of what they are).
- Only overrides the methods you say.
- Has strict mode (can't accidentally mock non-existent methods)
- Has an 'original()' function for wrapping methods (source):
my
$mock
= Test::MockModule->new(
"MyModule"
);
$mock
->redefine(
"something"
,
sub
{ my ($self, $args) = @_; push @record_for_testing_later, $args;
return
$mock
->original(
@_
);
});
- DBD::Mock - don't use this, use Test::DBIx::Class (with DBIx::Class::Fixtures) or a real test database.
- Test::Mock::Class - looks clunky
- Mock::Quick - another one, syntax may be better. "less side effects", doesn't reload modules.
- To call original un-mocked method, use (source):
my $mock = qtakeover 'Some::Module' => ( id => sub { my ( $mock_self, @args ) = @_; $counter->{id}++ if scalar @args; return $mock_self->MQ_CONTROL->original('id')->( $mock_self, @args ); }, );
Reasons to use Mock::Quick
- The syntax is more terse
- That's all. Test::MockModule also allows wrapping (i.e. calling the original module)
- The syntax is more terse
- That's all. Test::MockModule also allows wrapping (i.e. calling the original module)
Monkey patching modules
- Manually overriding subroutines AKA monkey patching - risky. What if you miss a method? Same as what Test::MockModule does.
- example: { package X; *method_name = sub { return 'foo'; } }
- Class::Monkey
- Monkey::Patch
- Mojo::Util::monkey_patch
- Sub::Override
- Mock::Sub - another one
- Mock::MonkeyPatch
- Can easily call original sub with Mock::MonkeyPatch::ORIGINAL
Rules of mocking
Don't monkey patch. Don't modify the symbol table directly like this:
*Acme::Foo::method = sub { return "mock you"; }
Instead use Test::MockModule or Sub::Override or similar. Reasons:
*Acme::Foo::method = sub { return "mock you"; }
Instead use Test::MockModule or Sub::Override or similar. Reasons:
- If you type the method name wrong, or if it gets moved, it becomes so obvious that it can't be missed
- You won't continue to test code that doesn't exist anymore
The first rule of mocking is: Don't mock.
Instead use dependency injection. If you think you want to use a mock:- instead expose the object as an attribute,
- and pass in one mock object once
- this is easier to develop & maintain than mocking different things in every test
- and less prone to leakage between tests
- create a test class like Foo::Test that inherits from Foo
- override the dangerous methods
- add test code: my $test_foo = Foo::Test->(@test_args)
- and then: $t = Test::Mojo->new("App", { foo => $test_foo });
- instantiate like this: $class = $config->foo || Foo->new(@args)
- if necessary, the main library and test library should share an abstract interface (implemented via a role)
Only if all the above fail, use MockModule. Note that scope can matter when using set_isa()