Passing arguments to a partial mock's constructor

You can't use Laravel's mock helper

Joel Clermont
Joel Clermont
2024-04-08

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.

Toss a coin in the jar if you found this helpful.
Want a tip like this in your inbox every weekday? Sign up below 👇🏼

Level up your Laravel skills!

Each 2-minute email has real-world advice you can use.