如何验证PHPUnit/Mockery中模拟方法指定调用的参数?
问题描述
现有类A的saveObjects方法接收数据数组,遍历调用BucketRepository的persist方法创建Bucket对象并持久化,最后调用flush。需要测试persist方法每次调用传入的Bucket对象的amount值是否符合预期(比如第一次为2、第二次为6等)。原PHPUnit的at匹配器已废弃,询问如何在PHPUnit或Mockery中实现该验证。
原代码示例
类A实现
class A { public function __construct(private BucketRepository $repository){} public function saveObjects(array $data) { foreach ($data as $row) { $this->repository->persist(new Bucket($row['amount'])); // 注:原代码此处误用$data,修正为$row以符合逻辑 } $this->repository->flush(); } }
测试代码片段(待补充)
public function testSaveObjects() { $mock = $this->createMock(BucketRepository::class); $subject = new A($mock); // 需补充验证逻辑 $mock->expects($this->exactly(3)) ->method('persist') ->with(....) $subject->saveObjects([ ['amount' => 2], ['amount' => 6], ['amount' => 9] ]); }
解决方案
一、使用PHPUnit实现验证
PHPUnit废弃at匹配器后,推荐通过withConsecutive或闭包回调的方式,逐个验证每次调用的参数。
方法1:用withConsecutive指定每次调用的匹配规则
withConsecutive可为每一次方法调用设置独立的参数匹配逻辑,结合callback检查Bucket对象的amount属性:
public function testSaveObjects() { $mock = $this->createMock(BucketRepository::class); $subject = new A($mock); // 验证persist被调用3次,每次传入的Bucket对象amount符合预期 $mock->expects($this->exactly(3)) ->method('persist') ->withConsecutive( [$this->callback(function (Bucket $bucket) { return $bucket->amount === 2; })], [$this->callback(function (Bucket $bucket) { return $bucket->amount === 6; })], [$this->callback(function (Bucket $bucket) { return $bucket->amount === 9; })] ); // 验证flush被调用一次 $mock->expects($this->once()) ->method('flush'); $subject->saveObjects([ ['amount' => 2], ['amount' => 6], ['amount' => 9] ]); }
方法2:用闭包捕获调用顺序并验证
如果需要更灵活的逻辑,可通过闭包捕获预期值数组,按调用顺序逐一验证:
public function testSaveObjects() { $mock = $this->createMock(BucketRepository::class); $subject = new A($mock); $expectedAmounts = [2, 6, 9]; $callIndex = 0; $mock->expects($this->exactly(count($expectedAmounts))) ->method('persist') ->with($this->callback(function (Bucket $bucket) use (&$callIndex, $expectedAmounts) { $currentExpected = $expectedAmounts[$callIndex]; $callIndex++; return $bucket->amount === $currentExpected; })); $mock->expects($this->once())->method('flush'); $subject->saveObjects([ ['amount' => 2], ['amount' => 6], ['amount' => 9] ]); }
二、使用Mockery实现验证
Mockery支持通过ordered()保证调用顺序,结合withArgs()和闭包实现参数验证,也可通过循环简化多组预期值的验证逻辑。
方法1:用ordered()按顺序验证参数
use Mockery; use PHPUnit\Framework\TestCase; class ATest extends TestCase { public function tearDown(): void { Mockery::close(); } public function testSaveObjects() { $mock = Mockery::mock(BucketRepository::class); $subject = new A($mock); // 按调用顺序验证每次persist的参数 $mock->shouldReceive('persist') ->once() ->withArgs(function (Bucket $bucket) { return $bucket->amount === 2; }) ->ordered(); $mock->shouldReceive('persist') ->once() ->withArgs(function (Bucket $bucket) { return $bucket->amount === 6; }) ->ordered(); $mock->shouldReceive('persist') ->once() ->withArgs(function (Bucket $bucket) { return $bucket->amount === 9; }) ->ordered(); // 验证flush被调用一次 $mock->shouldReceive('flush')->once(); $subject->saveObjects([ ['amount' => 2], ['amount' => 6], ['amount' => 9] ]); } }
方法2:循环遍历预期值简化代码
如果预期值数量较多,可通过循环批量设置验证规则:
public function testSaveObjects() { $mock = Mockery::mock(BucketRepository::class); $subject = new A($mock); $expectedAmounts = [2, 6, 9]; foreach ($expectedAmounts as $amount) { $mock->shouldReceive('persist') ->once() ->withArgs(function (Bucket $bucket) use ($amount) { return $bucket->amount === $amount; }) ->ordered(); } $mock->shouldReceive('flush')->once(); $subject->saveObjects([ ['amount' => 2], ['amount' => 6], ['amount' => 9] ]); }
内容的提问来源于stack exchange,提问作者Lehren
相关产品推荐
相关产品推荐

