jcid/phpunit-mock-extension

0.1.0 2016-01-10 10:53 UTC

This package is auto-updated.

Last update: 2024-08-23 19:34:26 UTC


README

一个 PHP 扩展,允许对方法进行多次调用,具有不同的计数和不同的参数。

安装

composer require jcid/phpunit-mock-extension

问题

目前,如果您想对 PHP Unit 模拟对象进行多次方法调用,具有不同的参数和返回语句,您可以使用 $this->at($index) 方法。这个方法的问题是不能检查方法是否被调用得太多次,比如下面的例子中的第三次调用。这是因为 PHPUnit 不使用参数来选择匹配器。

class ExampleTest extends PHPUnit_Framework_TestCase
{
    public function testExample()
    {
        $mock = $this->getMock(\stdClass::class, ['mymethod']);
        $mock->expects($this->at(0))
            ->method('mymethod')
            ->with('aaa')
            ->willReturn('bbb');
        $mock->expects($this->at(1))
            ->method('mymethod')
            ->with('bbb')
            ->willReturn('ccc');

        $this->assertSame('bbb', $mock->mymethod('aaa'));
        $this->assertSame('ccc', $mock->mymethod('bbb'));
        $mock->mymethod('different');
    }
}

另一个解决方案是使用逻辑或方法来定义参数。但我个人不喜欢这个方法,因为它将参数与返回值解耦,并且您无法控制参数和返回值的顺序或组合。

class ExampleTest extends PHPUnit_Framework_TestCase
{
    public function testLogicOr()
    {
        $mock = $this->getMock(\stdClass::class, ['mymethod']);
        $mock->expects($this->exactly(2))
            ->method('mymethod')
            ->with($this->logicalOr(
                $this->equalTo('aaa'),
                $this->equalTo('bbb')
            ))
            ->will($this->onConsecutiveCalls('bbb', 'ccc'));

        $this->assertSame('bbb', $mock->mymethod('aaa'));
        $this->assertSame('ccc', $mock->mymethod('bbb'));
    }
}

但还有第三个解决方案由 PHPUnit 提供,即 $this->returnValueMap(*array*) 方法。这个方法有点奇怪,因为数组的最后一个参数是返回值。但它将参数和返回值耦合起来,这是很好的。现在您也可以检查方法是否以正确的组合被调用多少次。

class ExampleTest extends PHPUnit_Framework_TestCase
{
    public function testReturnValueMap()
    {
        $mock = $this->getMock(\stdClass::class, ['mymethod']);
        $mock->expects($this->exactly(2))
            ->method('mymethod')
            ->will($this->returnValueMap([
                ['aaa', 'bbb'],
                ['bbb', 'ccc'],
            ]));

        $this->assertSame('bbb', $mock->mymethod('aaa'));
        $this->assertSame('ccc', $mock->mymethod('bbb'));
    }
}

使用 $this->returnValueMap(*array*) 方法的第二个问题是,参数使用严格的比较检查,如果您使用值持有者,比如我们这样,这将失败。

class ExampleTest extends PHPUnit_Framework_TestCase
{
    public function testReturnValueMapStrict()
    {
        $mock = $this->getMock(\stdClass::class, ['mymethod']);
        $mock->expects($this->exactly(2))
            ->method('mymethod')
            ->will($this->returnValueMap([
                [new DummyValueHolder('aaa'), 'bbb'],
                ['bbb', 'ccc'],
            ]));

        $this->assertSame('bbb', $mock->mymethod(new DummyValueHolder('aaa')));
        $this->assertSame('ccc', $mock->mymethod('bbb'));
    }
}

class DummyValueHolder
{
    private $data;

    public function __construct($data)
    {
        $this->data = $data;
    }
}

我们的解决方案

我们创建了这样一个库,它使用自定义 MockObject 匹配器和自定义 MockObject 来决定如何以及多少次返回特定的值。这个想法基于 $this->at($index) 的理念,但具有我们想要的全部功能。

我们使用自定义构建器来创建对特定方法调用序列,然后使用构建器创建的自定义匹配器和它提供的返回存根来定义 expects

use PHPUnit\Extensions\MockObject\Stub\ReturnMapping\Builder;

class ExampleTest extends PHPUnit_Framework_TestCase
{
    public function testSolution()
    {
        $builder = new Builder();
        $builder->expects($this->exactly(2))
            ->with(new DummyValueHolder('aaa'))
            ->willReturnSelf();
        $builder->expects($this->once())
            ->with('second method call matcher')
            ->willReturn('second return');
        $builder->expects($this->exactly(2))
            ->with('third method call matcher')
            ->willReturn('third return');

        $dummy = $this->getMock(\stdClass::class, ['test']);
        $dummy->expects($builder->matcher())->method('test')->will($builder->mapper());

        $this->assertSame($dummy, $dummy->test(new DummyValueHolder('aaa')));
        $this->assertSame($dummy, $dummy->test(new DummyValueHolder('aaa')));
        $this->assertSame('second return', $dummy->test('second method call matcher'));
        $this->assertSame('third return', $dummy->test('third method call matcher'));
        $this->assertSame('third return', $dummy->test('third method call matcher'));
    }
}

class DummyValueHolder
{
    private $data;

    public function __construct($data)
    {
        $this->data = $data;
    }
}

灵感

这个库受到了 etsy/phpunit-extensions 的启发。