NAME

Test::Mocha - Test Spy/Stub Framework

VERSION

version 0.15

SYNOPSIS

Test::Mocha is a test spy framework for testing code that has dependencies on other objects.

use Test::More tests => 2;
use Test::Mocha;
use Types::Standard qw( Int );

# create the mock
my $warehouse = mock;

# stub method calls (with type constraint for matching argument)
stub($warehouse)->has_inventory($item1, Int)->returns(1);

# execute the code under test
my $order = Order->new(item => $item1, quantity => 50);
$order->fill($warehouse);

# verify interactions with the dependent object
ok( $order->is_filled, 'Order is filled' );
verify( $warehouse, '... and inventory is removed' )->remove_inventory($item1, 50);

DESCRIPTION

We find all sorts of excuses to avoid writing tests for our code. Often it seems too hard to isolate the code we want to test from the objects it is dependent on. Mocking frameworks are available to help us with this. But it still takes too long to set up the mock objects before you can get on with testing the actual code in question.

Test::Mocha offers a simpler and more intuitive approach. Rather than setting up the expected interactions beforehand, you ask questions about interactions after the execution. The mocks can be created in almost no time. Yet they are ready to be used out-of-the-box by pretending to be any type you want them to be and accepting any method call on them. Explicit stubbing is only required when the dependent object is expected to return a response. After executing the code under test, you can selectively verify the interactions that you are interested in. As you verify behaviour, you focus on external interfaces rather than on internal state.

FUNCTIONS

mock

mock() creates a new mock object.

my $mock = mock;

By default, the mock object pretends to be anything you want it to be. Calling isa() or does() on the object will always return true.

ok( $mock->isa('AnyClass') );
ok( $mock->does('AnyRole') );
ok( $mock->DOES('AnyRole') );

It will also accept any method call on it. By default, any method call will return undef (in scalar context) or an empty list (in list context).

ok( $mock->can('any_method') );
is( $mock->any_method(@args), undef );

stub

stub() is used when you need a method to respond with something other than returning undef. Use it to tell a method to return some value(s) or to raise an exception.

stub($mock)->method_that_returns(@args)->returns(1, 2, 3);
stub($mock)->method_that_dies(@args)->dies('exception');

is_deeply( [ $mock->method_that_returns(@args) ], [ 1, 2, 3 ] );
ok( exception { $mock->method_that_dies(@args) } );

The stub applies to the exact method and arguments specified. (But see also "ARGUMENT MATCHING" for a shortcut around this.)

stub($list)->get(0)->returns('first');
stub($list)->get(1)->returns('second');

is( $list->get(0), 'first' );
is( $list->get(1), 'second' );
is( $list->get(2), undef );

A stubbed response will persist until it is overridden.

stub($warehouse)->has_inventory($item, 10)->returns(1);
ok( $warehouse->has_inventory($item, 10) ) for 1 .. 5;

stub($warehouse)->has_inventory($item, 10)->returns(0);
ok( !$warehouse->has_inventory($item, 10) ) for 1 .. 5;

You may chain responses together to provide a series of responses.

stub($iterator)->next
    ->returns(1)->returns(2)->returns(3)->dies('exhuasted');
ok( $iterator->next == 1 );
ok( $iterator->next == 2 );
ok( $iterator->next == 3 );
ok( exception { $iterator->next } );

Responses may also be specified as callbacks.

my @returns = qw( first second );

stub($list)->get(Int)->executes(sub {
    my ($i) = @_;
    die "index out of bounds" if $i < 0;
    return $returns[$i];
});

is( $list->get(0), 'first'  );
is( $list->get(1), 'second' );
is( $list->get(2), undef    );
like( exception { $list->get(-1) }, qr/^index out of bounds/ ),

verify

verify($mock, [%option], [$test_name])->method(@args)

verify() is used to test the interactions with the mock object. You can use it to verify that the correct methods were called, with the correct set of arguments, and the correct number of times. verify() plays nicely with Test::Simple and Co - it will print the test result along with your other tests and calls to verify() are counted in the test plan.

verify($warehouse)->remove($item, 50);
# prints: ok 1 - remove("book", 50) was called 1 time(s)

An option may be specified to constrain the test.

verify( $mock, times => 3 )->method(@args)
verify( $mock, at_least => 3 )->method(@args)
verify( $mock, at_most => 5 )->method(@args)
verify( $mock, between => [3, 5] )->method(@args)
times

Specifies the number of times the given method is expected to be called. The default is 1 if no other option is specified.

at_least

Specifies the minimum number of times the given method is expected to be called.

at_most

Specifies the maximum number of times the given method is expected to be called.

between

Specifies the minimum and maximum number of times the given method is expected to be called.

An optional $test_name may be specified to be printed instead of the default.

verify( $warehouse, 'inventory removed' )->remove_inventory($item, 50);
# prints: ok 1 - inventory removed

verify( $warehouse, times => 0, 'inventory not removed' )
    ->remove_inventory($item, 50);
# prints: ok 2 - inventory not removed

inspect

inspect() returns a list of method calls matching the given method call specification. It can be useful for debugging failed verify() calls. It may also be used in place of a complex verify() call to break it down into smaller tests.

my @method_calls = inspect($warehouse)->remove_inventory(Str, Int);

Each method call object has a name and an args property, and it is string overloaded.

is( $method_call->name, 'remove_inventory',       'method name' );
is_deeply( [$method_call->args], ['book', 50],    'method args array' );
is( $method_call, 'remove_inventory("book", 50)', 'method as string' );

clear

clear($mock)

Clears the method call history. Note that this does not affect the stubbed methods.

ARGUMENT MATCHING

Argument matchers may be used with stub(), verify() or inspect. They are type constraints that are used to match method arguments. They save you from having to specify the exact arguments. This is a powerful feature that will save you time when writing your stubs and verifications.

Predefined types

You may use any Type::Tiny type constraint such as those predefined in Types::Standard. (Moose type constraints such as MooseX::Types::Moose and MooseX::Types::Structured will also work.)

use Types::Standard qw( Any );

my $mock = mock;
stub($mock)->foo(Any)->returns('ok');

print $mock->foo(1);        # prints: ok
print $mock->foo('string'); # prints: ok

verify($mock, times => 2)->foo(Defined);
# prints: ok 1 - foo(Defined) was called 2 time(s)

You may use the normal features of type constraints: parameterized and structured types, and type unions, intersections and negations (but there's no need to use coercions).

use Types::Standard qw( Any ArrayRef HashRef Int StrMatch );

my $list = mock;
$list->set(1, [1,2]);
$list->set(0, 'foobar');

# parameterized type
# prints: ok 1 - set(Int, StrMatch[(?^:^foo)]) was called 1 time(s)
verify($list)->set( Int, StrMatch[qr/^foo/] );

Self-defined types

You may also use your own type constraints, defined using Type::Utils.

use Type::Utils -all;

# naming the type means it will be printed nicely in the verify() output
my $positive_int = declare 'PositiveInt', as Int, where { $_ > 0 };

# prints: ok 2 - set(PositiveInt, Any) was called 1 time(s)
verify($list)->set( $positive_int, Any );

Argument slurping

You may use the slurpy() function if you don't care what are arguments are used. They will just slurp up the remaining arguments as though they match. Note that empty argument lists are also recognised by slurpy types.

# prints: ok 3 - set({ slurpy: ArrayRef }) was called 2 time(s)
verify($list)->set( slurpy ArrayRef );

# prints: ok 4 - set({ slurpy: HashRef }) was called 2 time(s)
verify($list)->set( slurpy HashRef );

TO DO

  • Enhanced verifications

SUPPORT

Bugs / Feature Requests

Please report any bugs or feature requests by email to bug-test-mocha at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-Mocha. You will be automatically notified of any progress on the request by the system.

AUTHOR

Steven Lee <stevenwh.lee@gmail.com>

ACKNOWLEDGEMENTS

This module is a fork from Test::Magpie originally written by Oliver Charles (CYCLES).

It is inspired by the popular Mockito for Java and Python by Szczepan Faber.

SEE ALSO

Test::MockObject

COPYRIGHT AND LICENSE

This software is copyright (c) 2013 by Steven Lee.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.