Last week, I talked about how partial mocks don't run the constructor and how to work around it. That spawned a follow-up question from a reader: "How do you pass arguments to the constructor of a partial mock?"
To be honest, this is not something I've had to do before. It's pretty rare I'd use a partial mock in the first place, and it's even rarer that I'd need to run the constructor with arguments.
But I'm always up for digging in to source code and figuring something out though, so let's dig in.
The first thing to note is that you cannot use Laravel's partialMock
helper. We already established it won't run the constructor, but even more than that, it only allows you to pass a Closure
as the second argument. The assumption is this would only be used to perform custom assertions on your mock.
This will make more sense with some code. Here's what that partialMock
helper looks like:
protected function partialMock($abstract, Closure $mock = null)
{
return $this->instance($abstract, Mockery::mock(...array_filter(func_get_args()))->makePartial());
}
Notice how the second argument is type-hinted as a Closure
, and then it's passed in to the underlying Mockery::mock
call.
The Mockery::mock
method treats all its arguments as mixed, because it allows those arguments to be used in different ways, not only as a custom assertion callback.
In fact, one of the documented use cases is an array of arguments to be passed to the mocked constructor.
Because of all this, the solution is quite simple: Just use Mockery::mock
directly and bypass the Laravel mock
or partialMock
helpers.
To summarize:
// this will fail because of how Laravel type-hints the second argument
$mock = $this->mock(MyClass::class, ['arg1', 'arg2']);
// this works and passes the arguments to the constructor
$mock = Mockery::mock(MyClass::class, ['arg1', 'arg2']);
This is a good reminder of why it's important to understand the tools you're using. Laravel helpers save a ton of time and often provide a nicer developer experience, but sometimes they limit the options you can use with the underlying libraries.
In a case like this, it's perfectly valid to bypass the helpers and use the underlying libraries directly.
Hope this helps,
Joel
P.S. Do you have a Laravel question? Hit reply and share. I may answer it in a future tip.