3
votes

I am trying to mock this method:

$transformer = $this->transformerFactory->createProductTransformer($product, function (ProductInterface $product) use ($discount) {
    $product->setDiscount($discount);
});

It accepts callback parameter as a second argument and I don't know how to mock it.

I am using Mockery so that would look like that:

$this->transformerFactoryMock
    ->shouldReceive('createProductTransformer')
    ->with(???) // here!

If I pass the same callback to the with() method, the instances does not match. I don't mind using PHPUnit mocking if Mockery doesn't support that.

3

3 Answers

2
votes

If you need check exactly that callback is called then it is not possible as each \Closure instance is unique and inside your tested method you created new one.

But you can do next:

1) validate dynamic args for type

// Assume in test case you have these mock/stub
$discount;
$product;

$transformerFactoryMock = \Mockery::mock('TransformerFactory')
    ->shouldReceive('createProductTransformer')
    ->with($product, \Mockery::type(\Closure::class))

2) check any hidden args (created before call mocked method inside your testing method)

$transformerFactoryMock = \Mockery::mock('TransformerFactory')
    ->shouldReceive('createProductTransformer')
    ->with(function(...$args) use ($product) {
        // check any args
    })

3) finally, fake result using passed callback to mocked method

// then mock will be
$transformerFactoryMock = \Mockery::mock('TransformerFactory')
    ->shouldReceive('createProductTransformer')
    ->andReturnUsing(function($closure) {
        // execute closure
        $closure();

        // any logic to fake return
        return;
    })

Check doc of mockery complex argument matching and declaring return value

1
votes

If the "same callback" means identical code, then it's not the same callback for PHP and so Mockery will not accept it.

var_dump(function () {} === function () {}); // false
$func = function () {};
var_dump($func === $func); // true

To check callback type you can use mockery::type method (with argument 'closure') and for more precise checking there is mockery::on. https://github.com/padraic/mockery#argument-validation

0
votes

It's tough to tell exactly what you're trying to do in your test file from the snippet you pasted, but you can mock Closures and their params. Here's a quick/dirty/untested example that is a best guess at what you're trying to accomplish:

class Transformer {

    protected transformerFactory;

    public function __construct($factory) {
        $this->transformerFactory = $factor;
    }

    public function doSomething(Discount $discount, ProductInterface $product) {

        return $this->transformerFactory->createProductTransformer($product, function($product) use ($discount) {
            $product->setDiscount($discount);
        });
    }

}


class TransformerTest {

    protected function makeTransformerWithFakeFactory()
    {
        $fakeFactory = \Mockery::mock('TransformerFactory');

        $transformer = new Transformer($fakeFactory);

        return array($tranformer, $fakeFactory);
    }

    protected function fakeDiscount()
    {
        return \Mockery::mock('Discount');
    }

    protected function fakeProduct()
    {
        return \Mocker::mock('ProductInterface');
    }

            // first let's test to make sure that the factory's correct method is called with correct parameters
    function test_doSomething_WhenPassedProduct_CallsCreateProductTransformerOnFactoryWithProductAndClosure()
    {
        list($transformer, $mockFactory) = $this->makeTransformerWithFakeFactory();
        $fakeDiscount = $this->fakeDiscount();
        $fakeProduct = $this->fakeProduct();

        $mockFactory->shouldReceive('createProductTransformer')->once()->with($fakeProduct, \Mockery::type('Closure'));

        $transfomer->doSomething($fakeDiscount, $fakeProduct);
    }

            // now, let's test to make sure that the $discount within the closure is called with the right method and params
        function test_doSomething_createProductTransformerCalledWithProductAndClosure_CallsSetDiscountOnProductWithDiscount()
    {
        list($transformer, $stubFactory) = $this->makeTransfomerWithFakeFactory();
        $fakeDiscount = $this->fakeDiscount();
        $mockProduct = $this->fakeProduct();
        $stubFactory->shouldReceive('createProductTransfomer')->passthru();

        $mockProduct->shouldReceive('setDiscount')->once()->with($fakeDiscount);

        $transfomer->doSomething($fakeDiscount, $mockProduct);
    }

            // now lets make sure that doSomething returns what the call to factory's createProductTransformer method returns
    function test_doSomething_createProductTransformerCalledWithProductAndClosureReturnsValue_ReturnsSameValue()
    {
        list($transformer, $stubFactory) = $this->makeTransfomerWithFakeFactory();
        $fakeDiscount = $this->fakeDiscount();
        $fakeProduct = $this->fakeProduct();
        $stubFactory->shouldReceive('createProductTransfomer')->andReturn('transformed result');

        $result = $transfomer->doSomething($fakeDiscount, $fakeProduct);

        $this->assertEquals('transformed result', $result);
    }

}