Метод фиксации с параметром обратного вызова

Я пытаюсь издеваться над этим методом:

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

Он принимает параметр обратного вызова в качестве второго аргумента, и я не знаю, как имитировать его.

Я использую Mockery, чтобы это выглядело так:

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

Если я передаю тот же обратный вызов методу with (), экземпляры не совпадают. Я не против использовать насмешку PHPUnit, если Mockery этого не поддерживает.


person acid    schedule 20.01.2014    source источник


Ответы (3)


Если вам нужно точно проверить, вызывается ли обратный вызов, это невозможно, поскольку каждый экземпляр \Closure уникален, а внутри вашего протестированного метода вы создали новый.

Но можно сделать следующее:

1) проверить динамические аргументы для типа

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

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

2) проверьте все скрытые аргументы (созданные перед вызовом имитационного метода внутри вашего метода тестирования)

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

3) наконец, поддельный результат с использованием переданного обратного вызова издевательскому методу

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

        // any logic to fake return
        return;
    })

См. Документ с насмешливым сопоставлением сложных аргументов и объявление возвращаемого значения

person Alexander Zakharkin    schedule 02.06.2020

Если «тот же обратный вызов» означает идентичный код, тогда это не тот же обратный вызов для PHP, и Mockery его не примет.

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

Чтобы проверить тип обратного вызова, вы можете использовать метод mockery :: type (с аргументом closure), а для более точной проверки есть mockery :: on. https://github.com/padraic/mockery#argument-validation

person Josef Cech    schedule 21.01.2014

Трудно точно сказать, что вы пытаетесь сделать в своем тестовом файле из вставленного фрагмента, но вы можете имитировать Closures и их параметры. Вот быстрый / грязный / непроверенный пример, который лучше всего показывает, чего вы пытаетесь достичь:

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);
    }

}
person awei    schedule 03.06.2014