NAME
Test::MethodFixtures - Convenient mocking of externalities by recording and replaying method calls.
SYNOPSIS
Setting up mocked methods in a test script:
# my-test-script.t
...
use Test::MethodFixtures;
Test::MethodFixtures->new->mock("Their::Package::Method");
use My::Package; # has dependency on Their::Package::Method
... # unit tests for My::Package here
Example workflow using the mocked methods:
$> TEST_MF_MODE=record prove -l my-test-script.t
$> git add t/.methodfixtures
$> git commit -m 'methodfixtures for my-test-script.t'
$> prove -l my-test-script.t # uses saved data
More configuration options:
use Test::MethodFixtures
# optionally specify global arguments
mode => 'record',
storage => ...
;
my $mocker = Test::MethodFixtures->new(
mode => 'record', # set locally for this object
# optionally specify alternative storage:
# override default storage directory
dir => '/path/to/storage',
# use Test::MethodFixtures::Storage::File class (default)
storage => 'File',
# use alternative Test::MethodFixtures::Storage object
storage => $storage_obj,
# load alternative Test::MethodFixtures::Storage class
storage => '+Alt::Storage::Class',
# or:
storage => { '+Alt::Storage::Class' => \%options },
# without '+' prefix, 'Test::MethodFixtures::Storage' is prepended to name
);
# simple functions and class methods - can store all arguments
$mocker->mock("Their::Package::Method");
# object methods - we need to turn $_[0] ($self) into an
# unique identifier for object, not memory reference
$mocker->mock( "Their::Object::Method",
sub { $_[0]->firstname . '-' . $_[0]->lastname } );
# do the same for other arguments
$mocker->mock(
"Their::Object::Method",
sub {
( $_[0], # use as-is
$_[1]->firstname . '-' . $_[1]->lastname, # object in $_[1]
# no need to list further arguments if no more changes required
);
}
);
# skipping arguments that shouldn't be saved - set to undef
$mocker->mock(
"Their::Package::Method",
sub {
( $_[0], # keep
undef, # discard
# further args kept as-is
);
}
);
DESCRIPTION
Record and playback method arguments, for convenient mocking in tests.
With this module it is possible to easily replace an expensive, external or non-repeatable call, so that there is no need to make that call again during subsequent testing.
This module aims to be low-dependency to minimise disruption with legacy codebases. By default tries to use Test::MethodFixtures::Storage::File to record method data. Other storage classes can be provided instead, to use modules available to your system.
N.B. This module should be considered ALPHA quality and liable to change.
Despite not providing any test methods, it is under the Test::
namespace to aid discovery and because it makes little sense outside of a test environment. The name is inspired by database 'fixtures'.
Feedback welcome!
METHODS
new
my $mocker = Test::MethodFixtures->new(
{ mode => 'record', # override global / ENV
dir => '/path/to/storage', # override default storage directory
# or use alternative Test::MethodFixtures::Storage object
storage => $storage_obj,
# or load alternative Test::MethodFixtures::Storage:: class
storage => { 'Alt::Storage::Class' => \%options },
}
);
Class method. Constructor
mock
$mocker->mock("Their::Package::method");
$mocker->mock( "Their::Package::method", sub { ( $_[0], ... ) } );
In record
mode stores the return values of the named method against the arguments passed through to generate those return values.
In playback
mode retrieves stored return values of the named method for the arguments passed in.
In passthrough
mode the arguments and return values are passed to and from the method as normal (i.e. turns off mocking).
The arguments passed to the mocked method are used to create the key to store the results against.
Optionally mock()
takes a second argument of a coderef to manipulate @_
, for example to prevent storage of a non-consistent value or to stringify an object to a unique identifier.
store
Internal method. Wraps store()
on storage object, and adds package versions for this class and the storage class for comparison on retrieval.
retrieve
Internal method. Wraps retrieve()
on storage object, and checks package versions are compatible.
BEHAVIOUR
Warns if the module versions used to create the saved data is more recent than those currently running.
Handles calling context (list or scalar). Satisfies code using
wantarray
.
RATIONALE
Testing is good, but also hard to do well, especially with complex systems. This module aims to provide a simple way to help isolate code for testing, and get closer to true "unit testing".
Why not mock objects?
Mock objects are a good way to satisfy simple dependencies, but have many drawbacks, especially in complex systems:
They require writing of more code (more development time and more chances for bugs). The mocking code may end up being a duplication of existing behaviour of the mocked code.
They have to be kept up-to-date with the code that they are mocking, yet are not usually stored with that code or maintained by the same developers. Besides the extra development costs, divergence may only be noticed later and so the tests are of less value.
Further reading
https://www.destroyallsoftware.com/blog/2014/test-isolation-is-about-avoiding-mocks
SEE ALSO
SUPPORT
Bugs / Feature Requests
Please report any bugs or feature requests through the issue tracker at https://github.com/mjemmeson/Test-MethodFixtures/issues. You will be notified automatically of any progress on your issue.
Source Code
This is open source software. The code repository is available for public review and contribution under the terms of the license.
https://github.com/mjemmeson/Test-MethodFixtures
git clone git://github.com/mjemmeson/Test-MethodFixtures.git
AUTHOR
Michael Jemmeson <mjemmeson@cpan.org>
COPYRIGHT
Copyright 2015- Michael Jemmeson
LICENSE
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.