How to Mock Aws\S3\S3Client for phpunit // 如何模拟魔术方法

How to Mock Aws\S3\S3Client for phpunit // how to mock magic methods

我需要在我的 symfony5 项目中模拟 S3Client,以便能够引发异常并测试我的代码对这些异常的反应。我们使用 aws/aws-sdk-php 版本 3.*

7 年前有人遇到过同样的问题,我尝试按照这个解决方案 [PHPUnit - Mock S3Client not working well] 但是我遇到了错误。

我现在在做什么:

在我的服务中:

class AwsS3Service implements AwsS3ServiceInterface
{
    private S3Client $s3Client;
    ....

    public function __construct(ParameterBagInterface $params)
    {
        [ ... ]

        $this->s3Client = $this->getS3client();
    }

    public function getS3client(): S3Client
    {
        return (new Sdk($this->config))->createS3();
    }

所以我有一个 public 方法,我可以模拟它并让它返回一个模拟的 S3Client。

在我的测试中,我执行以下操作:

        $this->awsS3Service = $this->createMock(AwsS3ServiceInterface::class);

        $command = $this->createMock(Command::class);
        /** @var S3Client $s3Client */
        $s3Client = $this->createMock(S3Client::class);

        $s3Client->method('listObjectsV2')
                 ->willThrowException(new S3Exception('VALIDATION ERROR', $command));
        $s3Client->method('putObject')
                 ->willThrowException(new S3Exception('VALIDATION ERROR', $command));
        $s3Client->method('getObject')
                 ->willThrowException(new S3Exception('VALIDATION ERROR', $command));
        $s3Client->method('deleteObject')
                 ->willThrowException(new S3Exception('VALIDATION ERROR', $command));

        $this->awsS3Service
            ->method('getS3client')
            ->willReturn($s3Client);

现在,当我 运行 测试时,我得到了错误

Aws S3Service (App\Tests\Unit\Domain\AwsS3\AwsS3Service)
 ✘ Aws upload file s 3 exception
   ┐
   ├ PHPUnit\Framework\MockObject\MethodCannotBeConfiguredException: Trying to configure method "listObjectsV2" which cannot be configured because it does not exist, has not been specified, is final, or is static
   │
   ╵ /var/www/tests/Helper/AwsMockHelper.php:34
   ╵ /var/www/tests/Unit/Domain/AwsS3/AwsS3ServiceTest.php:35
   ┴

问题似乎是,这些方法在 S3Client class 中并不真正存在,只是在 class:

的 PhpDoc 中以某种方式定义
 * @method \Aws\Result listObjectsV2(array $args = [])

我们在编写服务时遵循了这个文档: https://docs.aws.amazon.com/sdk-for-php/v3/developer-guide/s3-examples-creating-buckets.html

当您知道问题的名称时,通常更容易找到解决方案。我相应地调整了问题标题。

在 class 的 header 中定义的方法实际上并不存在,被称为“魔术方法”,据此我找到了解决问题的方法:

这条 link 下的评论让我走上正轨:https://carstenwindler.de/php/mocking-magic-methods-with-phpunit/

在 phpunit 的当前版本 9.5 中,您可以使用 addMethods 和此语法来模拟魔术方法:

        $command = $this->createMock(Command::class);
        $s3Client = $this->getMockBuilder(S3Client::class)
                         ->disableOriginalConstructor()
                         ->addMethods(['listObjectsV2', 'putObject', 'getObject', 'deleteObject'])
                         ->getMock();

        $s3Client->expects($this->once())
                 ->method('listObjectsV2')
                 ->willThrowException(new AwsException('VALIDATION ERROR', $command));
        $s3Client->expects($this->once())
                 ->method('putObject')
                 ->willThrowException(new AwsException('VALIDATION ERROR', $command));
        $s3Client->expects($this->once())
                 ->method('getObject')
                 ->willThrowException(new AwsException('VALIDATION ERROR', $command));
        $s3Client->expects($this->once())
                 ->method('deleteObject')
                 ->willThrowException(new AwsException('VALIDATION ERROR', $command));