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

如何在PHPUnit 10+中Mock S3ClientInterface?

PHPUnit 10/11 中Mock AWS S3客户端的最优方案

在PHPUnit 10及以上版本中,addMethods被弃用后,直接Mock S3客户端的魔术方法(比如getObject、doesObjectExistV2)会因为PHPUnit拒绝Mock不存在的方法而报错,只Mock__call又太繁琐。针对这个问题,推荐以下几种靠谱方案:

方案一:使用AWS SDK官方Mock Handler(最推荐)

AWS SDK for PHP本身提供了专门的测试工具MockHandler,可以直接模拟S3客户端的请求和响应,完全不需要处理魔术方法的问题,这也是官方主推的测试方式。

代码示例:

use Aws\S3\S3Client;
use Aws\MockHandler;
use Aws\Result;

// 创建MockHandler实例
$mockHandler = new MockHandler();

// 添加预期响应:模拟doesObjectExistV2返回true
$mockHandler->append(new Result(['Exists' => true]));

// 添加预期响应:模拟getObject返回指定Body
$mockHandler->append(new Result(['Body' => 'foo']));

// 用MockHandler构建S3客户端
$s3 = new S3Client([
    'region' => 'us-east-1',
    'version' => 'latest',
    'handler' => $mockHandler,
]);

// 测试中正常调用方法即可
$this->assertTrue($s3->doesObjectExistV2('MockBucket', 'mockObjects/2d/cf90910456ce55e673dab13a76a3dfd8197aa7'));
$response = $s3->getObject([
    'Bucket' => 'MockBucket',
    'Key'    => 'mockObjects/2d/cf90910456ce55e673dab13a76a3dfd8197aa7',
]);
$this->assertEquals('foo', $response['Body']);

如果需要验证请求参数是否正确,可以结合CommandRecorderMiddleware捕获请求:

use Aws\Middleware;
use Psr\Http\Message\RequestInterface;

$capturedRequests = [];
$recorder = Middleware::tap(function (RequestInterface $request) use (&$capturedRequests) {
    $capturedRequests[] = $request;
});

// 把记录器中间件加到MockHandler
$handler = $mockHandler->append($recorder);

// 调用方法后检查捕获的请求
$s3->doesObjectExistV2('MockBucket', 'test-key');
$request = $capturedRequests[0];
$this->assertStringContainsString('/MockBucket/test-key', (string)$request->getUri());

方案二:自定义接口+Mock(适合必须用PHPUnit Mock的场景)

如果你的代码依赖S3ClientInterface,可以自己定义一个包含所需方法的接口——因为S3客户端的魔术方法特性,PHP会自动适配这个接口的调用,之后直接Mock这个自定义接口即可。

代码示例:

// 自定义包含所需方法的接口
interface MyS3ClientInterface
{
    public function doesObjectExistV2(string $bucket, string $key): bool;
    public function getObject(array $params): array;
}

// 测试中Mock这个自定义接口
$s3 = $this->createMock(MyS3ClientInterface::class);

$s3->expects($this->once())
    ->method('doesObjectExistV2')
    ->with('MockBucket', 'mockObjects/2d/cf90910456ce55e673dab13a76a3dfd8197aa7')
    ->willReturn(true);

$s3->expects($this->once())
    ->method('getObject')
    ->with(['Bucket' => 'MockBucket', 'Key' => 'mockObjects/2d/cf90910456ce55e673dab13a76a3dfd8197aa7'])
    ->willReturn(['Body' => 'foo']);

注意:要确保自定义接口的方法签名和S3客户端的实际调用逻辑一致,避免出现调用错误。

方案三:优化Mock __call方法的写法

如果以上两种方案都不适用,只能Mock__call的话,可以通过回调函数区分不同方法调用,减少重复代码:

$s3 = $this->createMock(S3ClientInterface::class);

$s3->expects($this->exactly(2))
    ->method('__call')
    ->willReturnCallback(function (string $method, array $arguments) {
        return match ($method) {
            'doesObjectExistV2' => $arguments[0] === 'MockBucket' && $arguments[1] === 'mockObjects/2d/cf90910456ce55e673dab13a76a3dfd8197aa7',
            'getObject' => $arguments[0] === ['Bucket' => 'MockBucket', 'Key' => 'mockObjects/2d/cf90910456ce55e673dab13a76a3dfd8197aa7'] ? ['Body' => 'foo'] : null,
            default => null,
        };
    });

这种方式比单独处理每个调用简洁,但还是不如官方Mock Handler直观,仅作为备选方案。

内容的提问来源于stack exchange,提问作者donatJ

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 13:07:23