Lzh on GitHub

保留传引用方法参数的行为

PHP 类方法可以接受引用传递的参数。在这种情况下,对参数(对传递给方法的原始变量的引用)所做的更改会反映在原始变量中。例如:

PHP 类方法可以接受引用传递的参数。在这种情况下,对参数(对传递给方法的原始变量的引用)所做的更改会反映在原始变量中。例如:

class Foo
{
    public function bar(&$a)
    {
        $a++;
    }
}

$baz = 1;
$foo = new Foo;
$foo->bar($baz);

echo $baz; // 将输出整数 2

在上面的例子中,变量 $baz 是通过引用传递给 Foo::bar() 的(注意参数前面的 & 符号了吗?)。bar() 对参数引用所做的任何更改都会反映在原始变量 $baz 中。

Mockery 可以正确处理所有可以通过 Reflection 分析参数以查看其是否为引用传递的方法。要模拟类方法如何操作引用的行为,我们可以使用闭包参数匹配器来操作它,即 \Mockery::on()——请参阅 复杂参数验证 章节。

对于内部 PHP 类,存在一个例外,Mockery 无法使用 Reflection 来分析方法参数(这是 PHP 的一个限制)。为了解决这个问题,我们可以使用 \Mockery\Configuration::setInternalClassMethodParamMap() 显式声明内部类的方法参数。

以下是使用 MongoCollection::insert() 的一个例子。MongoCollection 是由 PECL 的 mongo 扩展提供的内部类。它的 insert() 方法接受一个数据数组作为第一个参数,以及一个可选的选项数组作为第二个参数。原始数据数组会被更新(即当 insert() 是一个传引用参数时)以包含一个新的 _id 字段。我们可以使用配置的参数映射(告诉 Mockery 期望一个传引用参数)和一个附加到预期将被更新的方法参数上的 Closure 来模拟这种行为。

以下是一个 PHPUnit 单元测试,用于验证这种传引用行为被保留:

public function testCanOverrideExpectedParametersOfInternalPHPClassesToPreserveRefs()
{
    \Mockery::getConfiguration()->setInternalClassMethodParamMap(
        'MongoCollection',
        'insert',
        array('&$data', '$options = array()')
    );
    $m = \Mockery::mock('MongoCollection');
    $m->shouldReceive('insert')->with(
        \Mockery::on(function(&$data) {
            if (!is_array($data)) return false;
            $data['_id'] = 123;
            return true;
        }),
        \Mockery::any()
    );

    $data = array('a'=>1,'b'=>2);
    $m->insert($data);

    $this->assertTrue(isset($data['_id']));
    $this->assertEquals(123, $data['_id']);

    \Mockery::resetContainer();
}

受保护的方法

在处理受保护的方法,并试图保留它们的传引用行为时,需要采用不同的方法。

class Model
{
    public function test(&$data)
    {
        return $this->doTest($data);
    }

    protected function doTest(&$data)
    {
        $data['something'] = 'wrong';
        return $this;
    }
}
class Test extends \PHPUnit\Framework\TestCase
{
    public function testModel()
    {
        $mock = \Mockery::mock('Model[test]')->shouldAllowMockingProtectedMethods();

        $mock->shouldReceive('test')
            ->with(\Mockery::on(function(&$data) {
                $data['something'] = 'wrong';
                return true;
            }));

        $data = array('foo' => 'bar');

        $mock->test($data);
        $this->assertTrue(isset($data['something']));
        $this->assertEquals('wrong', $data['something']);
    }
}

这相当是一个边缘情况,所以我们需要稍微修改原始代码,通过创建一个公共方法来调用我们的受保护方法,然后模拟这个公共方法,而不是受保护的方法。这个新的公共方法将充当我们受保护方法的代理。