DEV Community

Peter Fox
Peter Fox

Posted on • Originally published at articles.peterfox.me on

Laravel Testing: Mocking static methods with the container


Photo by ShareGrid on Unsplash

I came across a difficult challenge this week. How can I mock a static method being used? Ideally, static methods shouldn’t be used as factories, but sometimes you don’t want to go down that rabbit hole of having to refactor everything.

Example Problem

Take the following code.

class Bar
{
    public function bar(mixed $bar): mixed
    {
        return Foo::foo($bar);
    }
}
Enter fullscreen mode Exit fullscreen mode

It’s simple enough, but what if we need to mock Foo::foo() for some particular reason, maybe it’s interacting with a database or something you don’t want to deal with in a continuous integration environment.

Either way, this could be difficult to do.

The Mockery Solution

If you’re a regular test writer for your Laravel applications you likely know you can use Mockery. Mocks are typically used for instances, but you can mock static methods as well.

public function test_it_works(): void
{
    Mockery::mock('alias:' . Foo::class)
        ->shouldReceive('bar')
        ->with('bar')
        ->andReturn('foo');

    $result = $bar->bar('bar');
    $this->assertSame('foo', $result);
}
Enter fullscreen mode Exit fullscreen mode

This works but if you dig into the alias functionality in Mockery, it can lead to issues where the library is unable to replace the existing class. This will cause the test to throw an exception.

The Laravel Container Solution

What you might not realise is that Laravel’s facades are a great way to get around this kind of problem. In this case we’ll be using the App facade.

First we need to make a minor change to our original class.

class Bar
{
    public function bar(mixed $bar): mixed
    {
        return App::call(Foo::foo(...), [$bar]);
    }
}
Enter fullscreen mode Exit fullscreen mode

What we have done is turn the static method into a closure and tell the container to execute the closure with the provided arguments.

Now we’ve done this we can write a test that instead uses the facade to create a mock.

public function test_it_works(): void
{
    App::shouldReceive('call')
        ->with(Mockery::type(\Closure), ['bar'])
        ->andReturn('foo');

    $bar = new Bar();
    $result = $bar->bar('bar');
    $this->assertSame('foo', $result);
}
Enter fullscreen mode Exit fullscreen mode

And that’s it.

Conclusion

I hope this has been a quick and helpful little article. I put this together mainly as a solution when you’re up against the clock. Obviously, I would advise against which kinds of static methods but it’s possible that sometimes you can’t get around it without lots of extra work. This small trick works great with that legacy code you’re still struggling with.

I’m Peter Fox, a software developer in the UK who works with Laravel. Thank you for reading my article, I’ve got several more available at https://articles.peterfox.me. I’m also now Sponsorable on GitHub. If you’d like to encourage me to write more articles like this please do consider dropping a small one-off donation.

Top comments (0)