NAME
Test::Mocha - Test double framework with method stubs and behaviour verification
VERSION
version 0.62
SYNOPSIS
Test::Mocha is a test double 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';
called_ok { $warehouse->remove_inventory($item1, 50) } '... and inventory is removed';
# clear the invocation history
clear $warehouse;
DESCRIPTION
Test::Mocha is a test double framework inspired by Java's Mockito. It offers a different approach to other mocking frameworks in that instead of setting up the expected behaviour beforehand you ask questions about interactions after execution of the system-under-test. This approach means there is less setup needed to use your test double which means you can focus more on testing, and it minimises the coupling of the tests to the implementation which means less maintenance of your test code.
Explicit stubbing is only required when the dependent object is expected to return a specific response. And you can even use argument matchers to skip having to enter the exact method arguments for the stub.
After executing the code under test, you can test that your code is interacting correctly with its dependent objects. Selectively verify the method calls that you are interested in only. As you verify behaviour, you focus on external interfaces rather than on internal state.
FUNCTIONS
mock
$mock = mock;
mock()
creates a new mock object. It's that quick and simple! It is ready out-of-the-box to pretend to be any object you want it to be and to accept any method calls on it.
Any public method may be called on mocks. By default the methods will return undef
or ()
depending on the context. See "stub" below for how to change this behaviour.
$result = $mock->method(@args); # returns undef
@result = $mock->method(@args); # returns ()
isa()
, does()
or DOES()
returns true for any class or role name. can()
returns a reference to the default public method. This is particularly handy when the dependent object needs to satisfy attribute type constraint checks with OO frameworks such as Moose.
$mock->isa('AnyClass'); # returns 1
$mock->does('AnyRole'); # returns 1
$mock->DOES('AnyRole'); # returns 1
$mock->can('any_method'); # returns a coderef
ref()
is a special method that you can stub to specify the value you would like returned when you use the ref()
function with a mock object.
stub { $mock->ref } returns 'SomeClass';
print ref($mock); # prints 'SomeClass'
spy
$spy = spy($object);
Don't want to abstract away the behaviour of an entire class? Use a spy. Spies act as wrappers to real objects. Rather than giving pretend responses as mocks do, they delegate the method calls to the real objects (including the UNIVERSAL methods like isa()
and DOES
) and return their actual responses. But the method calls can also be verified using "called_ok" or overridden using "stub".
This means you can use the existing behaviour of the object and fake only parts of it, such as a call to a server that's not available in your dev environment or that returns non-deterministic results.
stub
stub { $mock->method(@args) } returns(@values)
stub { $mock->method(@args) } throws($exception)
stub { $mock->method(@args) } executes($coderef)
By default, the mock object already acts as a stub that accepts any method call and returns undef
or ()
. However, you can use stub()
to tell a method to give an alternative response. You can specify 3 types of responses:
returns(@values)
-
Specifies that a stub should return 1 or more values.
stub { $mock->method(@args) } returns 1, 2, 3; print $mock->method(@args); # prints "123"
throws($message)
-
Specifies that a stub should raise an exception.
stub { $mock->method(@args) } throws 'an error'; $mock->method(@args); # croaks with "an error at test.t line 10."
executes($coderef)
-
Specifies that a stub should execute the given callback. The arguments used in the method call are passed on to the callback.
my @returns = qw( first second third ); stub { $list->get(Int) } executes { my ( $self, $i ) = @_; die "index out of bounds" if $i < 0; return $returns[$i]; }; print $list->get(0); # prints "first" print $list->get(1); # prints "second" print $list->get(5); # warns "Use of uninitialized value in print at test.t line 16." print $list->get(-1); # dies with "Index out of bounds at test.t line 10."
A stub applies to the exact mock, 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';
print $list->get(0); # prints "first"
print $list->get(1); # prints "second"
print $list->get(2); # nothing printed (since default stub returns an empty list)
Chain responses together to provide a consecutive series.
stub { $iterator->next }
returns(1), returns(2), returns(3), throws('exhausted');
print $iterator->next; # prints "1"
print $iterator->next; # prints "2"
print $iterator->next; # prints "3"
print $iterator->next; # croaks with "exhausted at test.t line 13."
The last stubbed response will persist until it is overridden.
stub { $warehouse->has_inventory($item, 10) } returns 1;
print( $warehouse->has_inventory($item, 10) ) for 1 .. 5; # prints "11111"
stub { $warehouse->has_inventory($item, 10) } returns '';
print( $warehouse->has_inventory($item, 10) ) for 1 .. 5; # nothing printed
called_ok
called_ok { $mock->method(@args) }
called_ok { $mock->method(@args) } times($n)
called_ok { $mock->method(@args) } atleast($n)
called_ok { $mock->method(@args) } atmost($n)
called_ok { $mock->method(@args) } between($m, $n)
called_ok { $mock->method(@args) } $test_name
called_ok()
is used to test the interactions with the mock object. You can use it to verify that the correct method was called, with the correct set of arguments, and the correct number of times. called_ok()
plays nicely with Test::Simple and Co - it will print the test result along with your other tests and you must count calls to called_ok()
in your test plans.
called_ok { $warehouse->remove($item, 50) };
# prints "ok 1 - remove("book", 50) was called 1 time(s)"
The following functions are available to verify the number of calls:
times
-
Specifies the number of times the given method is expected to be called.
times(1)
is the default if no option is specified.called_ok { $mock->method(@args) } times(3); # prints "ok 1 - method(@args) was called 3 time(s)"
Note:
times()
may clash with the built-in function with the same name. You may explicitly specify which one you want by qualifying it as×(3)
orCORE::times
. atleast
-
Specifies the minimum number of times the given method is expected to be called.
called_ok { $mock->method(@args) } atleast(3); # prints "ok 1 - method(@args) was called at least 3 time(s)"
atmost
-
Specifies the maximum number of times the given method is expected to be called.
called_ok { $mock->method(@args) } atmost(5); # prints "ok 1 - method(@args) was called at most 5 time(s)"
between
-
Specifies the minimum and maximum number of times the given method is expected to be called.
called_ok { $mock->method(@args) } between(3, 5); # prints "ok 1 - method(@args) was called between 3 and 5 time(s)"
An optional last argument $test_name
may be specified to be printed instead of the default.
called_ok { $warehouse->remove_inventory($item, 50) } 'inventory removed';
# prints "ok 1 - inventory removed"
called_ok { $warehouse->remove_inventory($item, 50) } times(0), 'inventory not removed';
# prints "ok 2 - inventory not removed"
inspect
@method_calls = inspect { $mock->method(@args) };
($method_call) = inspect { $warehouse->remove_inventory(Str, Int) };
$method_call->name; # "remove_inventory"
$method_call->args; # ("book", 50)
$method_call->caller; # ("test.pl", 5)
$method_call->stringify; # 'remove_inventory("book", 50)'
$method_call->stringify_long; # 'remove_inventory("book", 50) called at test.pl line 5'
inspect()
returns a list of method call objects that match the given method call specification. It is used to inspect the methods that have been called on the mock object. It can be useful for debugging failed called_ok()
calls. Or use it in place of a complex call to called_ok()
to break it down in smaller tests.
The method call objects have the following accessor methods:
name
- The name of the method called.args
- The list of arguments passed to the method call.caller
- The file and line number from which the method was called.stringify
- The name and arguments as a string.stringify_long
- The name, arguments, file and line number as a string.`
They are also string overloaded with the value from stringify
.
inspect_all
@all_method_calls = inspect_all $mock
inspect_all()
returns a list of all methods called on the mock object. This is mainly used for debugging.
clear
clear $mock1, $mock2, ...
Clears the method call history for one or more mocks so that they can be reused in another test. Note that this does not clear the methods that have been stubbed.
ARGUMENT MATCHING
Argument matchers may be used in place of specifying exact method arguments. They allow you to be more general and will save you much time in your method specifications to stubs and verifications. Argument matchers may be used with stub()
, called_ok()
and inspect
.
Pre-defined types
You may use any of the ready-made types in Types::Standard. (Alternatively, Moose types like those in 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
called_ok { $mock->foo(Defined) } times(2);
# prints: ok 1 - foo(Defined) was called 2 time(s)
You may use the normal features of the types: 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)
called_ok { $list->set( Int, StrMatch[qr/^foo/] ) };
Self-defined types
You may also use your own types, defined using Type::Utils.
use Type::Utils -all;
# naming the type means it will be printed nicely in called_ok()'s output
my $positive_int = declare 'PositiveInt', as Int, where { $_ > 0 };
# prints: ok 2 - set(PositiveInt, Any) was called 1 time(s)
called_ok { $list->set($positive_int, Any) };
Argument slurping
SlurpyArray
and SlurpyHash
are special argument matchers exported by Test::Mocha that you can use when you don't care what arguments are used. They will just slurp up the remaining arguments as though they match.
called_ok { $list->set(SlurpyArray) };
called_ok { $list->set(Int, SlurpyHash) };
Because they consume the remaining arguments, you can't use further argument validators after them. But you can, of course, use them before. Note also that they will match empty argument lists.
MOCKING CLASS METHODS (experimental)
class_mock
class_mock()
creates a mock for stubbing class methods and module functions.
class_mock 'Some::Class';
To stub a class method or module function:
stub { Some::Class->some_class_method() } returns 'something';
is( Some::Class->some_class_method(), 'something' );
stub { Some::Class::some_module_function() } returns 'something';
is( Some::Class::some_module_function(), "something" );
Validation is handled similarly.
called_ok { Some::Class->some_class_method() } "some_class_method called";
called_ok { Some::Class::some_module_function() } "some_module_function called";
Note: The original class that you mock must not be imported before you have finished stubbing the class. This means that if your module under test use
s it, then you must use_ok
or require
the test module after stubbing the mock class.
Also note: Currently you cannot stub new().
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 https://rt.cpan.org/Public/Bug/Report.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.
It is not associated with the Javascript test framework for node.js called Mocha. I named Test::Mocha before that came about.
The class_mock()
function was contributed by Scott Davis.
SEE ALSO
COPYRIGHT AND LICENSE
This software is copyright (c) 2015 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.
DISCLAIMER OF WARRANTY
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.