NAME
Test::MockPackages - Mock external dependencies in tests
VERSION
Version 0.2
SYNOPSIS
my $m = Test::MockPackages->new();
# basic mocking
$m->pkg( 'ACME::Widget' )
->mock( 'do_thing' )
->expects( $arg1, $arg2 )
->returns( $retval );
# ensure something is never called
$m->pkg( 'ACME::Widget' )
->mock( 'dont_do_other_thing' )
->never_called();
# complex expectation checking
$m->pkg( 'ACME::Widget' )
->mock( 'do_multiple_things' )
->is_method() # marks do_multiple_things() as a method
->expects( $arg1, $arg2 ) # expects & returns for call #1
->returns( $retval )
->expects( $arg3, $arg4, $arg5 ) # expects & returns for call #2
->returns( $retval2 );
# using the mock() sub.
my $m = mock({
'ACME::Widget' => {
do_thing => [
expects => [ $arg1, $arg2 ],
returns => [ $retval ],
],
dont_do_other_thing => [
never_called => [],
],
do_multiple_things => [
is_method => [],
expects => [ $arg1, $arg2 ],
returns => [ $retval ],
expects => [ $arg3, $arg4, $arg5 ],
returns => [ $retval2 ],
],
},
'ACME::ImprovedWidget' => {
...
},
});
DESCRIPTION
Test::MockPackages is a package for mocking other packages as well as ensuring those packages are being used correctly.
Say we have a Weather class that can return the current degrees in Fahrenheit. In order to do this it uses another class, Weather::Fetcher which makes an external call. When we want to write a unit test for Weather, we want to mock the functionality of Weather::Fethcer.
Here is the sample code for our Weather class:
package Weather;
use Moose;
use Weather::Fetcher;
sub degrees_f {
my ( $self, $zip_code ) = @_;
my $data = eval { Weather::Fetcher::fetch_weather( $zip_code ) };
if ( !$data ) {
return;
}
return $data->{temp_f} . "°F";
}
And here's how we may choose to test this class. In the success
subtest, we use the mock() helper subroutine, and in the failure
method we use the OOP approach. Both provide identical functionality.
use Test::More;
use Test::MockPackages qw(mock);
subtest 'degrees_f' => sub {
subtest 'success' => sub {
my $m = mock({
'Weather::Fetcher' => {
fetch_weather => [
expects => [ '14202' ],
returns => [ { temp_f => 80 } ],
],
},
});
isa_ok( my $weather = Weather->new, 'Weather' );
is( $weather->degrees_f( 14202 ), '80°F', 'correct temperature returned' );
};
subtest 'failure' => sub {
my $m = Test::MockPackages->new();
$m->pkg( 'Weather::Fetcher' )
->mock( 'fetch_weather' )
->expects( '14202' )
->returns();
my $weather = Weather->new;
is( $weather->degrees_f( 14202 ), undef, 'no temperature returned' );
};
};
done_testing();
When we run our tests, you can see that Test::MockPackages validates the following for us: 1. the subroutine is called with the correct arguments, 2. the subroutine was called the correct number of times. Lastly, Test::MockPackages allows us to have this mocked subroutine return a consistent value.
ok 1 - The object isa Weather
ok 2 - Weather::Fetcher::fetch_weather expects is correct
ok 3 - correct temperature returned
ok 4 - Weather::Fetcher::fetch_weather called 1 time
1..4
ok 1 - success
ok 1 - Weather::Fetcher::fetch_weather expects is correct
ok 2 - no temperature returned
ok 3 - Weather::Fetcher::fetch_weather called 1 time
1..3
ok 2 - failure
1..2
ok 1 - degrees_f
1..1
For more information on how to properly configure your mocks, see Test::MockPackages::Mock.
IMPORTANT NOTE
When the Test::MockPackages object is destroyed, it performs some final verifications. Therefore, it is important that the object is destroyed before done_testing() is called, or before the completion of the script execution. If your tests are contained within a block (e.g. a subtest, do block, etc) you typically don't need to worry about this. If all of your tests are in the top level package or test scope, you may want to undef your object at the end.
Example where we don't have to explicitly destroy our object:
subtest 'my test' => sub {
my $m = mock({ ... });
# do tests
}; # in this example, $m will be destroyed at the end of the subtest and that's OK.
done_testing();
Example where we would have to explicitly destroy our object:
my $m = mock({ ... });
# do tests
undef $m;
done_testing();
CONSTRUCTOR
new( )
Instantiates and returns a new Test::MockPackages object.
You can instantiate multiple Test::MockPackages objects, but it's not recommended you mock the same subroutine/method within the same scope.
my $m = Test::MockPackages->new();
$m->pkg('ACME::Widget')->mock('do_thing')->never_called();
if ( ... ) {
my $m2 = Test::MockPackages->new();
$m2->pkg('ACME::Widget')->mock('do_thing')->called(2); # ok
}
my $m3 = Test::MockPackages->new();
$m3->pkg('ACME::Widget')->mock('do_thing')->called(3); # not ok
$m3->pkg('ACME::Widget')->mock('do_thing_2')->never_called(); # ok
Both this package, and Test::MockPackages::Package are light-weight packages intended to maintain scope of your mocked subroutines and methods. The bulk of your mocking will take place on Test::MockPackages::Mock objects. See that package for more information.
METHODS
pkg( Str $pkg_name ) : Test::MockPackages::Package
Instantiates a new Test::MockPackages::Package object using for $pkg_name
. Repeated calls to this method with the same $pkg_name
will return the same object.
Return value: A Test::MockPackages::Package object.
EXPORTED SUBROUTINES
mock( HashRef $configuration ) : Test::MockPackages
mock()
is an exportable subroutine (not exported by default) that allows you to quickly configure your mocks in one call. Behind the scenes, it converts your $configuration
to standard OOP calls to the Test::MockPackages, Test::MockPackages::Package, and Test::MockPackages::Mock packages.
$configuration
expects the following structure:
{
$package_name => {
$sub_or_method_name => [
$option => [ 'arg1', ... ],
],
}
...
}
$package_name
is the name of your package. This is equvalent to the call:
$m->pkg( $package_name )
$sub_or_method_name
is the name of the subroutine or method that you'd like to mock. This is equivalent to:
$m->pkg( $package_name )
->mock( $sub_or_method_name )
The value for $sub_or_method_name
should be an ArrayRef. This is so we can support having multiple expects
and returns
.
$option
is the name of one of the methods you can call in Test::MockPackages::Mock (e.g. called
, never_called
, is_method
, expects
, returns
). The value for $option
should always be an ArrayRef. This is equivalent to:
$m->pkg( $package_name )
->mock( $sub_or_method_name )
->$option( @{ [ 'arg1', ... ] } );
returns_code(&)( CodeRef $coderef ) : Test::MockPackages::Returns
Imported from Test::MockPackages::Returns. See that package for more information.
SEE ALSO
AUTHOR
Written by Tom Peters <tpeters at synacor.com>.
COPYRIGHT
Copyright (c), Synacor, Inc., 2016