A piggy bank of commands, fixes, succinct reviews, some mini articles and technical opinions from a (mostly) Perl developer.

Jump to

Quick reference

Mock modules & Rules of mocking for Perl tests

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

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:
  • 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
Or if you need a mock method to be in place when an app is instantiated, add logic in the attribute to load a custom object from config, or default to the real object if nothing found in config:
  • 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()