Revision history for Test::Mockingbird - Advanced mocking library for Perl with support for dependency injection and spies
0.10 Fri May 8 15:11:42 EDT 2026
Bug fixes
- _parse_target() used !defined $arg3 to detect the single-argument
shorthand form ('Pkg::method'). Because no caller ever passes three
arguments to _parse_target(), $arg3 is always undef, making the guard
permanently true. As a result, spy('A::B', 'method') was misread as
shorthand and resolved to package 'A', method 'B' -- silently spying
on the wrong target. Fixed by changing the discriminator to
!defined $arg2: the shorthand form has exactly one argument (arg2
undef), the longhand form has two (arg2 defined). All other callers
pass a single string argument and are unaffected.
- mock() emitted "Prototype mismatch: sub ... ()" warnings whenever it
replaced a function that carried a Perl prototype (such as the ()
no-args prototype used by I18N::LangTags::Detect::detect). The
warning fired because the replacement coderef had no prototype, so
Perl flagged the redefinition as a signature mismatch. Fixed by
calling Scalar::Util::set_prototype on the replacement immediately
before installing it, copying the prototype from the original coderef.
The call uses the & sigil (&Scalar::Util::set_prototype) to bypass
set_prototype's own (&$) prototype constraint, which would otherwise
reject a lexical variable as the first argument. unmock() required
no change: reinstating the original coderef via glob assignment
restores its prototype automatically.
- Scalar::Util is now loaded (use Scalar::Util ()) without importing
set_prototype into the Test::Mockingbird namespace, avoiding any
risk of the imported alias inheriting the (&$) prototype constraint
at the call site.
Notes
- spy() installs its wrapper coderef directly without going through
mock(), so it does not benefit from the set_prototype fix and still
emits a prototype-mismatch warning when wrapping a prototyped
function. This is a known limitation documented in the test suite
and will be addressed separately.
Tests
- Added prototype-preservation subtests to all four test files:
unit.t Six white-box subtests confirming that prototype()
on the installed glob matches the original after
mock() for (), ($$), ($), and no-prototype functions,
including across stacked mocks and after unmock.
function.t Three subtests capturing $SIG{__WARN__} and asserting
no prototype-mismatch warning is emitted during the
mock/unmock cycle for (), ($$), and no-prototype
functions. Return-value assertions on () functions
use ->can() to bypass Perl's compile-time constant
inlining of () prototype functions.
integration.t Three end-to-end subtests covering plain mock(),
deep_mock(), and mock_scoped() on a () prototype
function. All use ->can() closures for return-value
assertions; the plain mock() subtest uses the
fully-qualified Test::Mockingbird::restore_all() to
avoid shadowing by the TimeTravel restore_all import.
edge-cases.t Five boundary-condition subtests: () warning
suppression; stacked mocks each independently carrying
the prototype; mock_scoped() delegating correctly to
mock(); spy() documented as a known limitation that
still emits the mismatch warning; and ($$) full cycle.
0.09 Mon May 4 20:22:49 EDT 2026
Bug fixes
- mock_scoped was recording two diagnostic meta layers per call: one of
type 'mock' (emitted by the internal mock() call) and a second of type
'mock_scoped' (pushed explicitly afterwards). diagnose_mocks() therefore
reported depth 2 and a misleading 'mock' entry for every mock_scoped
installation. Fixed by setting local $TYPE = 'mock_scoped' before
delegating to mock(), matching the pattern already used by mock_return,
mock_exception, mock_sequence, and mock_once. Each mock_scoped call now
records exactly one layer of the correct type.
New features
- mock_scoped now accepts multiple method/coderef pairs in a single call,
returning one guard that restores all of them on destruction. Four
argument forms are supported:
Single shorthand (unchanged):
my $g = mock_scoped 'Pkg::method' => sub { ... };
Single longhand (unchanged):
my $g = mock_scoped('Pkg', 'method', sub { ... });
Multi shorthand -- pairs of fully-qualified-name, coderef:
my $g = mock_scoped(
'Pkg::fetch' => sub { ... },
'Other::save' => sub { ... },
);
Multi longhand -- package followed by method/coderef pairs:
my $g = mock_scoped('Pkg',
fetch => sub { ... },
save => sub { ... },
remove => sub { ... },
);
All methods covered by a multi-method guard are restored atomically when
the guard goes out of scope or is explicitly undefed.
- Test::Mockingbird::Guard updated to store a list of fully-qualified
method names rather than a single name, enabling the multi-method
mock_scoped forms above. Single-method behaviour is unchanged.
Tests
- Added t/mock_scoped_multi.t (39 assertions) covering: the meta-layer
bug fix; no-regression checks on both single-method forms; all three
new multi-method forms (multi shorthand, multi longhand two methods,
multi longhand three methods); explicit guard undef; and
diagnose_mocks() state before and after guard destruction.
0.08 Tue Apr 7 18:49:17 EDT 2026
Test::Mockingbird::DESTROY now calls restore_all
0.07 Mon Mar 23 20:15:20 EDT 2026
- Added Test::Mockingbird::TimeTravel:
* Integration to Test::Mockingbird::DeepMock.
* New 'now' plan supporting freeze, travel, advance, rewind.
* Automatic restoration of time state after deep_mock block.
* Deterministic interaction between mocks, spies, and frozen time.
* Added integration test exercising mixed mocking + time travel.
0.06 Fri Mar 20 08:13:40 EDT 2026
- Add restore(): restore all mock layers for a single method target.
- Added diagnose_mocks() and diagnose_mocks_pretty() for structured and
human-readable inspection of active mock layers,
including type and installation location.
0.05 Thu Mar 19 19:05:22 EDT 2026
- DeepMock:
- Added args_eq and args_deeply expectation types for exact and deep argument matching
- Added never expectation type to assert that a spy was not called
- Meets PBP level 5
- Disallow setting a mock to undef
- Add mock_return, mock_exception, and mock_sequence sugar helpers for common mocking patterns.
- Add mock_once: a one-shot mock that restores itself after the first call.
0.04 Thu Mar 19 08:36:23 EDT 2026
- Refactored unmock() to reliably support both shorthand and longhand targets.
- Added DeepMock
0.03 Wed Mar 4 07:29:58 EST 2026
Added shorthand syntax
Added mock_scoped
0.02 Thu Jan 9 08:05:34 EST 2025
Updated spy.t to check that the spied routine is called,
and spying is stopped after restore
More tests
0.01 Wed Jan 8 13:22:13 EST 2025
First draft