You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何验证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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 01:37:05