保留传引用方法参数的行为
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']);
}
}
这相当是一个边缘情况,所以我们需要稍微修改原始代码,通过创建一个公共方法来调用我们的受保护方法,然后模拟这个公共方法,而不是受保护的方法。这个新的公共方法将充当我们受保护方法的代理。