NAME
Test::Mockingbird::DeepMock - Declarative, structured mocking and spying for Perl tests
VERSION
Version 0.05
SYNOPSIS
use Test::Mockingbird::DeepMock qw(deep_mock);
{
package MyApp;
sub greet { "hello" }
sub double { $_[1] * 2 }
}
deep_mock(
{
mocks => [
{
target => 'MyApp::greet',
type => 'mock',
with => sub { "mocked" },
}, {
target => 'MyApp::double',
type => 'spy',
tag => 'double_spy',
},
], expectations => [
{
tag => 'double_spy',
calls => 2,
},
],
},
sub {
is MyApp::greet(), 'mocked', 'greet() was mocked';
MyApp::double(2);
MyApp::double(3);
}
);
DESCRIPTION
Test::Mockingbird::DeepMock provides a declarative, data-driven way to describe mocking, spying, injection, and expectations in Perl tests.
Instead of scattering mock, spy, and restore_all calls throughout your test code, DeepMock lets you define a complete mocking plan in a single hashref, then executes your test code under that plan.
This produces tests that are:
easier to read
easier to maintain
easier to extend
easier to reason about
DeepMock is built on top of Test::Mockingbird, adding structure, expectations, and a clean DSL.
WHY DEEP MOCK?
Traditional mocking in Perl tends to be:
imperative
scattered across the test body
difficult to audit
easy to forget to restore
DeepMock solves these problems by letting you declare everything up front:
deep_mock(
{
mocks => [...],
expectations => [...],
},
sub { ... }
);
This gives you:
a single place to see all mocks and spies
automatic restore of all mocks
structured expectations
reusable patterns
a clean separation between setup and test logic
PLAN STRUCTURE
A DeepMock plan is a hashref with the following keys:
mocks
An arrayref of mock specifications. Each entry is a hashref:
{
target => 'Package::method', # required
type => 'mock' | 'spy' | 'inject',
with => sub { ... }, # for mock/inject
tag => 'identifier', # for spies or scoped mocks
scoped => 1, # optional
}
Types
mock-
Replaces the target method with the provided coderef.
spy-
Wraps the method and records all calls. Must have a
tag. inject-
Injects a value or behavior into the target (delegates to
Test::Mockingbird::inject).
expectations
An arrayref of expectation specifications. Each entry is a hashref:
{
tag => 'double_spy', # required
calls => 2, # optional
args_like => [ # optional
[ qr/foo/, qr/bar/ ],
],
}
Expectation fields
tag-
Identifies which spy this expectation applies to.
calls-
Expected number of calls.
args_eq-
Arrayref of arrayrefs. Each inner array lists exact argument values expected for a specific call. Values are compared with
Test::More::is. args_deeply-
Arrayref of arrayrefs. Each inner array lists deep structures to compare against the arguments for a specific call. Uses
Test::Deep::cmp_deeply. args_like-
Arrayref of arrayrefs of regexes. Each inner array describes expected arguments for a specific call.
never-
Asserts that the spy was never called. Mutually exclusive with
calls.
globals
Optional hashref controlling global behavior:
globals => {
restore_on_scope_exit => 1, # default
}
COOKBOOK
Mocking a method
mocks => [
{
target => 'MyApp::greet',
type => 'mock',
with => sub { "hi" },
},
]
Spying on a method
mocks => [
{
target => 'MyApp::double',
type => 'spy',
tag => 'dbl',
},
]
Injecting a dependency
mocks => [
{
target => 'MyApp::Config::get',
type => 'inject',
with => { debug => 1 },
},
]
Expecting a call count
expectations => [
{
tag => 'dbl',
calls => 3,
},
]
Expecting argument patterns
expectations => [
{
tag => 'dbl',
args_like => [
[ qr/^\d+$/ ], # first call
[ qr/^\d+$/ ], # second call
],
},
]
Full example
deep_mock(
{
mocks => [
{ target => 'A::foo', type => 'mock', with => sub { 1 } },
{ target => 'A::bar', type => 'spy', tag => 'bar' },
],
expectations => [
{ tag => 'bar', calls => 2 },
],
},
sub {
A::foo();
A::bar(10);
A::bar(20);
}
);
TROUBLESHOOTING
"Not enough arguments for deep_mock"
You are using the BLOCK prototype form:
deep_mock {
...
}, sub { ... };
This only works if deep_mock has a (&$) prototype AND the first argument is a real block, not a hashref.
DeepMock uses ($$) to avoid Perl's block-vs-hashref ambiguity.
Use parentheses instead:
deep_mock(
{ ... },
sub { ... }
);
"Type of arg 1 must be block or sub {}"
You are still using the BLOCK prototype form. Switch to parentheses.
"Use of uninitialized value in multiplication"
Your spied method is being called with no arguments during spy installation. Make your method robust:
sub double { ($_[1] // 0) * 2 }
My mocks aren't restored
Ensure you didn't disable automatic restore:
globals => { restore_on_scope_exit => 0 }
Nested deep_mock scopes are not supported
DeepMock installs mocks using Test::Mockingbird, which provides only global restore semantics via restore_all. Because Test::Mockingbird does not expose a per-method restore API, DeepMock cannot safely restore only the mocks installed in an inner scope.
As a result, nested calls like:
deep_mock { ... } sub {
deep_mock { ... } sub {
...
};
};
will cause the inner restore to remove the outer mocks as well.
DeepMock therefore does not support nested mocking scopes.
deep_mock
Run a block of code with a set of mocks and expectations applied.
Purpose
Provides a declarative wrapper around Test::Mockingbird that installs mocks, runs a code block, and then validates expectations such as call counts and argument patterns.
Arguments
$plan- HashRefA plan describing mocks and expectations. Keys:
mocks- ArrayRef of mock specificationsEach specification includes:
-
target- "Package::method" -type- "mock" or "spy" -with- coderef for mock behavior (mock only) -tag- identifier for later expectationsexpectations- ArrayRef of expectation specificationsEach specification includes:
-
tag- spy tag to validate -calls- expected call count -args_like- regex argument matching -args_eq- exact argument matching -args_deeply- deep structural matching -never- assert spy was not called
$code- CodeRefThe block to execute while mocks are active.
Returns
Nothing. Dies on expectation failure.
Side Effects
Temporarily installs mocks and spies into the target packages. All mocks are removed after the code block completes.
Notes
This routine does not support nested deep_mock scopes. All mocks are global until restored.
API
Input (Params::Validate::Strict)
{
mocks => ArrayRef,
expectations => ArrayRef,
},
CodeRef
Output (Returns::Set)
returns: undef
SUPPORT
This module is provided as-is without any warranty.
Please report any bugs or feature requests to bug-test-mockingbird at rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-Mockingbird. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
You can find documentation for this module with the perldoc command.
perldoc Test::Mockingbird::DeepMock