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::Spy - build mocked interfaces and examine call data easily

SYNOPSIS

        use Test::More;
        use Test::Spy;

        # create a spy
        my $spy = Test::Spy->new;

        # add a method
        $spy->add_method('mocked_method')
                ->should_return('return value');

        # create an object, execute the test
        my $object = $spy->object;
        print $object->mocked_method('argument'); # 'return value'

        # examine the results
        my $method = $spy->method('mocked_method');
        ok $method->was_called;
        is_deeply $method->called_with, ['argument'];

        # examine the results - with context
        $spy->set_context('mocked_method');
        ok $spy->was_called;
        is_deeply $spy->called_with, ['argument'];

        done_testing;

DESCRIPTION

Test::Spy is a simple package for creating OO interface mocks, which can be verified on a per-method basis after the calls have been made.

This module will not replace methods in packages for you. It requires you to inject the generated object into your code with dependency injection.

Beta quality: interface is not yet stable before version 1.000.

Properties

base

Base object or package to use. Objects produced by Test::Spy will mimic the interface of its base object.

If an object is passed, it will be used directly. Otherwise, $base->new will be called.

Test::Spy will only work properly with objects that are blessed hash references with no extra behavior like inside-out objects.

optional on the constructor with name: base

writer: set_base

predicate: has_base

imitates

Accepts package name or array reference of package names.

Causes isa, DOES and does methods return true value if they are used to check the resulting object with the same package name. Similar to "base", but only affects object identity and will not call any methods or construct an object from class names. Useful if you want to make sure it passes some type checks, but will not actually call any unforeseen methods present in the base class.

        $spy->set_imitates('Some::Package');

        $spy->object->isa('Some::Package'); # true
        $spy->object->does('Some::Package'); # true
        $spy->object->DOES('Some::Package'); # true
        $spy->object->isa('Other::Package'); # false

optional on the constructor with name: imitates

writer: set_imitates

predicate: has_imitates

interface

Strictness of the object interface. Can be any of:

  • 'strict' (default) - methods which were not mocked don't exist (exception)

  • 'lax' - non-mocked methods are autoloaded and return undef

  • 'warn' - same as 'lax', but a warning is issued

optional on the constructor with name: interface

object

The actual object with mocked interface. Its gets cached after generation and must be generated again after changing "base".

context

String - name of the method which you are currently testing. Can be utilized if you're currently focusing on a specific method, or have mocked just one. This allows calling "Call history methods" without using an intermediate variable for it.

Can be passed to the constructor or set with the writer. The method does not have to exist during setting its name - the code will throw exception only when you try to actually test it.

optional on the constructor with name: context

writer: set_context

predicate: has_context

clearer: clear_context

General methods

new

Typical Moose-flavored constructor. See property list above for details on hash keys it handles.

add_method

        # add a method
        my $method = $spy->add_method($name, @returns)

        # call it
        $spy->object->$name;

Adds a new method with name $name to the object.

@returns array is optional and specifies the return value of the new method. If not present, method will return undef. See "should_return" in Test::Spy::Method for more details.

Return value is an object of Test::Spy::Method.

add_observer

Same as "add_method", but observers don't return and do not interrupt calls to methods of the base object.

Return value is an object of Test::Spy::Observer.

method

        my $method = $spy->method($name);

Returns an object of Test::Spy::Method or Test::Spy::Observer that was added with "add_method" or "add_observer" as $name.

Throws an exception if there is no such method.

clear_all

Calls ->clear on all registered methods and ->clear_context. Can be used to run a test again without re-creating the spy.

Call history methods

These methods are also found in Test::Spy::Method's interface. When called on Test::Spy, these methods will execute on the method which name is currently in "context". If there is no context, these methods will throw an exception.

call_history

Returns the entire call history - array reference of array references:

        [
                # first method call
                ['first method argument', 'second method argument', ...],

                # second method call
                ['first method argument', 'second method argument', ...],

                ...
        ]

This data is very raw, so its often better to use helpers specified below.

called_times

Returns a non-negative integer number - the number of times the method was called.

was_called

Returns a boolean - whether the method was called at least once.

If passed an argument, returns whether the method was called exactly as many times:

        $object->method;
        $object->method;

        $spy->set_context('method');

        $spy->was_called;    # true
        $spy->was_called(1); # false
        $spy->was_called(2); # true
        $spy->was_called(3); # false

wasnt_called

A shortcut for $spy->was_called(0) - see above.

was_called_once

A shortcut for $spy->was_called(1) - see above.

first_called_with

Returns an array reference - the arguments of the first (oldest) method call.

In addition, sets the internal method iterator to the beginning for "next_called_with".

If there were no calls at all, returns undef.

The behavior of the iterator functions are showcased below:

        $object->method(1);
        $object->method(2);

        $spy->set_context('method');

        $spy->first_called_with; # [1]
        $spy->called_with;       # [1]
        $spy->next_called_with;  # [2]
        $spy->next_called_with;  # undef
        $spy->last_called_with;  # [2]

        $object->method(3, 4);

        $spy->called_with;       # [2]
        $spy->next_called_with;  # [3, 4]
        $spy->first_called_with; # [1]

        $object->method;

        $spy->last_called_with;  # []

next_called_with

Returns an array reference - the arguments of the next (newer) method call.

The internal iterator of the method is increased by 1 as a result.

If there were no more calls, returns undef.

last_called_with

Returns an array reference - the arguments of the last (newest) method call.

In addition, sets the internal method iterator to the end for "next_called_with".

If there were no calls at all, returns undef.

called_with

Returns an array reference - the arguments of the current method call. If there was no other X_called_with call before, acts the same as "first_called_with".

The internal iterator of the method is not altered, unless it was not set at all, in which case it is set to the beginning.

Can return undef if there was no such call data.

clear

Clears all call and iterator data from the method.

SEE ALSO

Test::MockObject

AUTHOR

Bartosz Jarzyna <bbrtj.pro@gmail.com>

COPYRIGHT AND LICENSE

Copyright (C) 2022 by Bartosz Jarzyna

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