tmarsteel/mockery-callable-mock
Create Mockery-friendly callable/function mocks in PHP. Set expectations (with args, counts), return values, stubs, and verify calls or non-calls. Can also wrap a real callable to spy while still invoking it. Requires PHP 7+.
Installation
composer require --dev tmarsteel/mockery-callable-mock
Add to composer.json under require-dev to avoid production bloat.
First Use Case: Mocking Closures
use Mockery\MockInterface;
use Tmarsteel\MockeryCallableMock\CallableMock;
$mock = CallableMock::mock(function() { return 'original'; });
$mock->shouldReceive('__invoke')
->once()
->with('test')
->andReturn('mocked');
// Usage
$result = $mock('test'); // Returns 'mocked'
First Use Case: Spying on Closures (New in v2.1.0)
$spy = CallableMock::spy(function() { return 'original'; });
$spy('test'); // Returns 'original' but records the call
// Verify invocation
$spy->mockery->mockery_getNumberOfInvocations(); // Returns 1
Where to Look First
CallableMock class for core functionality (including new spy() method)Mocking Anonymous Functions
$callback = function($x) { return $x * 2; };
$mock = CallableMock::mock($callback)
->shouldReceive('__invoke')
->with(5)
->andReturn(10);
$result = $mock(5); // Returns 10
Spying on Closures (New in v2.1.0)
$spy = CallableMock::spy(function($x) { return $x * 2; });
$result = $spy(5); // Returns 10 (original behavior)
$spy->mockery->mockery_getLastInvocationArgs(); // Returns [5]
Integration with Laravel
ServiceProvider:
$this->app->bind(function ($app) {
$spy = CallableMock::spy(fn() => 'default');
return $spy;
});
Partial Mocking
$mock = CallableMock::mock($originalClosure)
->shouldReceive('__invoke')
->withArgs(function ($arg) {
return str_contains($arg, 'test');
})
->andReturn('partial');
Mockery::onConsecutiveCalls() or Mockery::with() for complex scenarios.Closure(string): int).PHPUnit for seamless test doubles in HttpTests, FeatureTests, or UnitTests.No Modern Mockery Support
^1.4).~1.3 in composer.json or fork the package.Limited Documentation
Closure Scope Issues
$this references break).Closure::bind() or static methods.Spy Behavior Clarity (New in v2.1.0)
shouldReceive() after spying.$spy = CallableMock::spy(fn() => 'original')
->shouldReceive('__invoke')
->with('override')
->andReturn('modified');
if (!$mock->mockery->mockery_getNumberOfInvocations()) {
throw new \RuntimeException('Mock/spy was never called!');
}
$mock->mockery->mockery_getLastInvocationArgs();
$spy->mockery->mockery_shouldHaveReceived('__invoke', Mockery::times(1));
Custom Matchers
Extend CallableMock to add domain-specific argument validation:
class CustomCallableMock extends CallableMock {
public function withValidEmail() {
return $this->with(function ($arg) {
return filter_var($arg, FILTER_VALIDATE_EMAIL);
});
}
}
Laravel Facade Create a helper facade for convenience:
// app/Providers/AppServiceProvider.php
Mockery::alias('mockCallable', \Tmarsteel\MockeryCallableMock\CallableMock::class);
Usage:
$mock = Mockery::mockCallable($closure)->shouldReceive('__invoke')...
$spy = Mockery::mockCallable($closure)->spy();
Performance
Hybrid Mock-Spy Patterns (New in v2.1.0) Combine spying and mocking for complex scenarios:
$hybrid = CallableMock::spy(fn() => 'default')
->shouldReceive('__invoke')
->with('special')
->andReturn('handled');
'special' return 'handled'.'default' and are tracked.How can I help you explore Laravel packages today?