Question

For learning purposes I was trying to write a unit test for one of my Magento 2 around plugins (interception).

The method I'm testing receives as parameter a \Closure object that cannot be mocked because \Closure is a final class

How do I proceed in this case? Create an actual closure in my test class and pass that as parameter? Or any other way?

Let's say my method looks like this. Ignore the content, focus on the concept.

class Something
{
    public function aroundDoStuff(SomeModel $subject \Closure $closure, $extraParam)
    {
        if ($subject->getSomeValue() == 1) {
             return 'Custom return';
        }
        return $closure($extraParam);
    }
}

Now for my test, I mocked the first parameter SomeModel.

$subject = $this->getMock(SomeModel::class, [], [], '', false);
$subject->expects($this->any())->method('getSomeValue')->will($this->returnValue('1'));

$closure = ????; // WHAT GOES HERE
$obj = new Something();
$expected = 'Custom return';
$extraParam = 'not important';
$this->assertEquals($expected, $obj->aroundDoStuff($subject, $closure, $extraParam));
Was it helpful?

Solution

To mock callables, I usually mock __invoke:

$callbackMock = $this->getMockBuilder(\stdClass::class)
    ->setMethods(['__invoke'])
    ->getMock();

$callbackMock->expects($this->once())->method('__invoke'); // etc.

The problem is that Magento uses \Closure type hints instead of callable. As soon as Magento supports PHP 7.1 you will be able to use Closure::fromCallable($callbackMock), until then, wrap it yourself:

$closure = function(...$args) use ($callbackMock) {
    return $callbackMock(...$args);
};

That aside, I would not bother writing unit tests for plugins most of the time. If there is business logic that I write with unit tests, this would be in another class where the plugin delegates to. To test if the plugin works correctly, an integration test is more appropiate.

OTHER TIPS

Whereas the upper solution works, it's deprecated by PHPUnit.

Instead, you can do:

$this->getMockBuilder(\stdClass::class)->setMethods(['__invoke'])->getMock();
Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top