NAME
Test::Mockingbird::Async - Future-based async mocking for Test::Mockingbird
VERSION
Version 0.12
SYNOPSIS
use Test::Mockingbird;
use Test::Mockingbird::Async qw(
mock_future_return
mock_future_fail
mock_future_sequence
mock_future_once
async_spy
);
# Mock a method to return a pre-resolved Future
mock_future_return 'My::DB::fetch' => { id => 1 };
my $result = My::DB::fetch()->get; # { id => 1 }
# Mock a method to return a pre-failed Future
mock_future_fail 'My::DB::fetch' => 'not found';
my ($msg) = My::DB::fetch()->failure; # 'not found'
# Return different values over successive calls
mock_future_sequence 'My::DB::fetch' => (10, 20, 30);
My::DB::fetch()->get; # 10
My::DB::fetch()->get; # 20
My::DB::fetch()->get; # 30 (repeated from here)
# Return a Future exactly once, then restore the previous implementation
mock_future_once 'My::DB::ping' => 'ok';
My::DB::ping()->get; # 'ok', then original restored
# Spy on a method that returns a Future
my $spy = async_spy 'My::DB::fetch';
My::DB::fetch('key');
my @calls = $spy->();
my $call = $calls[0];
# $call->{args} is [ 'My::DB::fetch', 'key' ]
# $call->{future} is the Future returned by the original
restore_all();
DESCRIPTION
Test::Mockingbird::Async extends Test::Mockingbird with helpers for testing code that uses Future-based asynchronous APIs.
All mocks and spies installed by this module use the same underlying stack as the core engine. restore_all(), unmock(), and diagnose_mocks() work identically to their core counterparts. Every installed layer is recorded in diagnose_mocks() with a type of mock_future_return, mock_future_fail, mock_future_sequence, mock_future_once, or async_spy.
async_spy also writes to the call-order log, so assert_call_order() works across a mix of plain spies and async spies.
Dependency
This module requires the Future distribution (available from CPAN). It is not a mandatory dependency of Test::Mockingbird itself; the module loads Future on first use and croaks with a helpful message if it is not installed.
METHODS
mock_future_return
Mock a method so that it always returns a pre-resolved Future.
mock_future_return 'My::DB::fetch' => $value;
mock_future_return 'My::DB::fetch' => ($val1, $val2); # multi-value
The method is replaced with a stub that returns Future-done(@values)>. Restore with restore_all() or unmock().
API specification
Input (Params::Validate::Strict schema)
- target: required, scalar string; shorthand 'Pkg::method' form - @values: zero or more values passed to Future-done>
Output (Returns::Set schema)
- return: undef
mock_future_fail
Mock a method so that it always returns a pre-failed Future.
mock_future_fail 'My::DB::fetch' => 'not found';
mock_future_fail 'My::DB::fetch' => ('db error', 'db', { code => 500 });
The method is replaced with a stub that returns Future-fail($message, @details)>. The caller receives a rejected Future; no exception is thrown at the call site.
API specification
Input (Params::Validate::Strict schema)
- target: required, scalar string - $message: required, scalar string; the failure message - @details: optional; additional failure metadata passed to Future-fail>
Output (Returns::Set schema)
- return: undef
mock_future_sequence
Mock a method so that it returns a sequence of Future values over successive calls. When the sequence is exhausted, the last item is repeated.
mock_future_sequence 'My::DB::fetch' => (10, 20, 30);
My::DB::fetch()->get; # 10
My::DB::fetch()->get; # 20
My::DB::fetch()->get; # 30 (repeats)
Each item in the sequence may be either a plain value (wrapped automatically in Future-done>) or a pre-built Future object (passed through as-is, allowing a mix of resolved and failed Futures in the sequence):
mock_future_sequence 'My::DB::fetch' =>
42,
Future->fail('oops');
API specification
Input (Params::Validate::Strict schema)
- target: required, scalar string - @items: required; one or more plain values or Future objects
Output (Returns::Set schema)
- return: undef
mock_future_once
Install a mock that returns a pre-resolved Future exactly once. After the first call the previous implementation is automatically restored.
mock_future_return 'My::DB::ping' => 'baseline';
mock_future_once 'My::DB::ping' => 'temporary';
My::DB::ping()->get; # 'temporary'
My::DB::ping()->get; # 'baseline' (previous mock restored)
This is useful for simulating transient failures, one-time responses, or state transitions in async code.
API specification
Input (Params::Validate::Strict schema)
- target: required, scalar string - @values: zero or more values passed to Future-done>
Output (Returns::Set schema)
- return: undef
async_spy
Wrap a method so that every call is recorded along with the Future it returned. The original method is still called and its return value is passed back to the caller unchanged.
my $spy = async_spy 'My::DB::fetch';
My::DB->fetch(id => 1);
my @calls = $spy->();
my $call = $calls[0];
# $call->{args} is [ 'My::DB::fetch', $invocant, id => 1 ]
# $call->{future} is whatever Future the original returned
my $result = $call->{future}->get; # inspect resolved value
Unlike the plain spy() coderef (which returns arrayrefs), async_spy returns hashrefs so the future field is unambiguous.
async_spy writes to the call-order log, so assert_call_order() works across a mix of plain and async spies.
Limitations
async_spy assumes the spied method returns a Future. If the method returns a plain value the future field in each call record will hold that value rather than a Future, and calling ->get on it will fail.
API specification
Input (Params::Validate::Strict schema)
- target: required; 'Pkg::method' shorthand or ('Pkg', 'method') longhand
Output (Returns::Set schema)
- return: coderef; when called, returns the list of call records
SUPPORT
This module is provided as-is without any warranty.
Please report bugs at https://github.com/nigelhorne/Test-Mockingbird/issues.
AUTHOR
Nigel Horne, <njh at nigelhorne.com>
SEE ALSO
LICENCE AND COPYRIGHT
Copyright 2026 Nigel Horne.
Usage is subject to the GPL2 licence terms. If you use it, please let me know.