The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Test::MockPackages::Mock - handles mocking of individual methods and subroutines.

VERSION

Version 1.00

SYNOPSIS

my $m = Test::MockPackages::Mock->new( $package, $subroutine )
   ->is_method()
   ->expects( $arg1, $arg2 )
   ->returns( 'ok' );

DESCRIPTION

Test::MockPackages::Mock will mock an individual subroutine or method on a given package. You most likely won't initialize new Test::MockPackages::Mock objects directly, instead you should have Test::MockPackages create them for you using the mock() method.

In short this package will allow you to verify that a given subroutine/method is: 1) called the correct number of times (see called(), never_called(), and is_method), 2) called with the correct arguments (see expects()), and 3) returns values you define (returns()).

Examples

Here's a trivial example. We have a subroutine, calculate() that uses an external dependency, ACME::Widget::do_something() to help calculate our value.

sub calculate {
    my ( $input ) = @ARG;

    return ACME::Widget::do_something( $input, 'CONSTANT' );
}

When we test our calculate() subroutine, we can mock the ACME::Widget::do_something() call:

subtest 'calculate()' => sub {
    my $m = Test::MockPackages->new();
    $m->pkg('ACME::Widget')
      ->mock('do_something')
      ->expects( 15, 'CONSTANT' )
      ->returns( 20 );

   is( calculate( 15 ), 20, 'correct value returned from calculate' );
};

The test output will look something like:

ok 1 - ACME::Widget::do_something expects is correct
ok 2 - correct value returned from calculate
ok 3 - ACME::Widget::do_something called 1 time

CONSTRUCTORS

new( Str $package_name, Str $name )

Instantiates a new Test::MockPackage::Mock object. $name is the subroutine or method that you intend to mock in the named $package_name.

METHODS

called( Int $called ) : Test::MockPackage::Mock, Throws '...'

Will ensure that the subroutine/method has been called $called times. This method cannot be used in combination with never_called().

Setting $called to -1 will prevent invocation count checks.

You can combined this method with expects() and/or returns() to support repeated values. For example:

$m->expects($arg1, $arg2)
  ->expects($arg1, $arg2)
  ->expects($arg1, $arg2)
  ->expects($arg1, $arg2)
  ->expects($arg1, $arg2);

can be simplified as:

$m->expects($arg1, $arg2)
  ->called(5);

By default, this package will ensure that a mocked subroutine/method is called the same number of times that expects() and/or returns() has been setup for. For example, if you call expects() three times, then when this object is destroyed we will ensure the mocked subroutine/method was called exactly three times, no more, no less.

Therefore, you only need to use this method if you don't setup any expects or returns, or to simplify repeated values like what was shown up above.

Return value: Returns itself to support the fluent interface.

never_called() : Test::MockPackage::Mock, Throws '...'

Ensures that this subroutine/method will never be called. This method cannot be used in combination with called(), expects(), or returns().

Return value: Returns itself to support the fluent interface.

is_method() : Test::MockPackage::Mock, Throws '...'

Specifies that the mocked subroutine is a method. When setting up expectations using expects(), it will ignore the first value which is typically the object.

Return value: Returns itself to support the fluent interface.

expects( Any @expects ) : Test::MockPackage::Mock, Throws '...'

Ensures that each invocation has the correct arguments passed in. If the subroutine/method will be called multiple times, you can call expects() multiple times. If the same arguments are expected repeatedly, you can use this in conjunction with called(). See called() for further information.

If you are mocking a method, be sure to call is_method() at some point.

When the Test::MockPackages::Mock object goes out of scope, we'll test to make sure that the subroutine/method was called the correct number of times based on the number of times that expects() was called, unless called() is specified.

The actual comparison is done using Test::Deep::cmp_deeply(), so you can use any of the associated helper methods to do a comparison.

use Test::Deep qw(re);

$m->mock( 'my_sub' )
  ->expects( re( qr/^\d{5}\z/ ) );

Return value: Returns itself to support the fluent interface.

returns( Any @returns ) : Test::MockPackage::Mock, Throws '...'

This method sets up what the return values should be. If the return values will change with each invocation, you can call this method multiple times. If this method will always return the same values, you can call returns() once, and then pass in an appropriate value to called().

When the Test::MockPackages::Mock object goes out of scope, we'll test to make sure that the subroutine/method was called the correct number of times based on the number of times that expects() was called, unless called() is specified.

Values passed in will be returned verbatim. A deep clone is also performed to accidental side effects aren't tested. If you don't want to have your data deep cloned, you can use returns_code.

$m->mock('my_sub')
  ->returns( $data_structure ); # $data_structure will be deep cloned using Storable::dclone();

$m->mock('my_sub')
  ->returns( returns_code { $data_structure } ); # $data_structure will not be cloned.

If you plan on returning a Test::MockObject object, you will want to ensure that it's not deep cloned (using returns_code) because that module uses the object's address to keep track of mocked methods (instead of using attributes).

wantarray will be used to try and determine if a list or a single value should be returned. If @returns contains a single element and wantarray is false, the value at index 0 will be returned. Otherwise, a list will be returned.

If you'd rather have the value of a custom CODE block returned, you can pass in a CodeRef wrapped using a returns_code from the Test::MockPackages::Returns package.

use Test::MockPackages::Returns qw(returns_code);
...
$m->expects( $arg1, $arg2 )
  ->returns( returns_code {
      my (@args) = @ARG;

      return join ', ', @args;
  } );

Return value: Returns itself to support the fluent interface.

AUTHOR

Written by Tom Peters <tpeters at synacor.com>.

COPYRIGHT

Copyright (c) 2016 Synacor, Inc.