java - 如何模拟 - 从 s3 读取文件

标签 java amazon-web-services unit-testing amazon-s3 mockito

我是编写单元测试的新手。我正在尝试读取存储在 S3 中的 JSON 文件,但收到“传递给 when() 的参数不是模拟!”和“配置文件不能为空”错误。

这就是目前我已经尝试过的方法Retrieving Object Using JAVA :

private void amazonS3Read() {
    String clientRegion = "us-east-1";
    String bucketName = "version";
    String key = "version.txt";
    S3Object fullObject = null;
    try {
        AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
            .withRegion(clientRegion)
            .withCredentials(new ProfileCredentialsProvider())
            .build();
        fullObject = s3Client.getObject(new GetObjectRequest(bucketName, key));
        S3ObjectInputStream s3is = fullObject.getObjectContent();
        json = returnStringFromInputStream(s3is);
        fullObject.close();
        s3is.close();
    } catch (AmazonServiceException e) {
        // The call was transmitted successfully, but Amazon S3 couldn't process
        // it, so it returned an error response.
        e.printStackTrace();
    } catch (SdkClientException e) {
        // Amazon S3 couldn't be contacted for a response, or the client
        // couldn't parse the response from Amazon S3.
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    //Do some operations with the data
}

测试文件

 @Test
 public void amazonS3ReadTest() throws Exception {
     String bucket = "version";
     String keyName = "version.json";
     InputStream inputStream = null;
     S3Object s3Object = Mockito.mock(S3Object.class);
     GetObjectRequest getObjectRequest = Mockito.mock(GetObjectRequest.class);

     getObjectRequest = new GetObjectRequest(bucket, keyName);

     AmazonS3 client = Mockito.mock(AmazonS3.class);
     Mockito.doNothing().when(AmazonS3ClientBuilder.standard());
     client = AmazonS3ClientBuilder.standard()
         .withRegion(clientRegion)
         .withCredentials(new ProfileCredentialsProvider())
         .build();

     Mockito.doReturn(s3Object).when(client).getObject(getObjectRequest);
     s3Object = client.getObject(getObjectRequest);

     Mockito.doReturn(inputStream).when(s3Object).getObjectContent();
     inputStream = s3Object.getObjectContent();
     //performing other operations
 }

获取两个不同的异常:

Argument passed to when() is not a mock! Example of correct stubbing: doThrow(new RuntimeException()).when(mock).someMethod();

org.mockito.exceptions.misusing.NotAMockException: 
Argument passed to when() is not a mock!
Example of correct stubbing: 

profile file cannot be null

java.lang.IllegalArgumentException: profile file cannot be null
at com.amazonaws.util.ValidationUtils.assertNotNull(ValidationUtils.java:37)
at com.amazonaws.auth.profile.ProfilesConfigFile.<init>(ProfilesConfigFile.java:142)
at com.amazonaws.auth.profile.ProfilesConfigFile.<init>(ProfilesConfigFile.java:133)
at com.amazonaws.auth.profile.ProfilesConfigFile.<init>(ProfilesConfigFile.java:100)
at com.amazonaws.auth.profile.ProfileCredentialsProvider.getCredentials(ProfileCredentialsProvider.java:135)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.getCredentialsFromContext(AmazonHttpClient.java:1184)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.runBeforeRequestHandlers(AmazonHttpClient.java:774)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:726)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:719)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:701)
at com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:669)
at com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:651)
at com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:515)
at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:4443)
at com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:4390)
at com.amazonaws.services.s3.AmazonS3Client.getObject(AmazonS3Client.java:1427)

我做错了什么,我该如何解决?

最佳答案

您的方法看起来不对。

  • 您想模拟私有(private)方法的依赖项和调用:amazonS3Read(),并且您似乎想对该方法进行单元测试。
    我们不对类的私有(private)方法进行单元测试,而是从其 API(应用程序编程接口(interface))即 public/protected 方法测试类。
  • 您的单元测试是一系列模拟记录:其中大部分是通过 Mockito 描述您的私有(private)方法的作用。我什至很难确定没有 mock 的部分.... 你在这里断言什么?你在一些模拟上调用了 4 种方法?不幸的是,它在结果/行为方面没有任何断言。您可以在被调用的方法之间添加不正确的调用,并且测试将保持绿色,因为您没有测试可以使用 assertEquals(...) 惯用法断言的结果。
    这并不意味着模拟一个方法是 Not Acceptable ,但是当你的测试主要是模拟时,就会出现问题,我们可以相信它的结果。

我会建议你两件事:

  • 编写一个单元测试,侧重于断言您执行的逻辑:计算/转换/传输值等等...不要侧重于链接方法。

  • 使用一些轻量级和简单的 S3 兼容服务器编写一些集成测试,这将为您提供行为断言方面的真实反馈。可以通过这种方式测试副作用。
    例如,您有 Riak , MinIo还是Localstack .


更具体地说,这是一种改进方法的重构方法。
如果必须对 amazonS3Read() 私有(private)方法进行单一测试,您可能应该将其移至特定类(例如 MyAwsClient)并将其设为公共(public)方法。

那么想法就是让amazonS3Read()在责任方面尽可能明确。
它的逻辑可以概括为:

1) 获取一些标识符信息传递给S3服务。
这意味着用参数定义了一个方法:

public Result amazonS3Read(String clientRegion, String bucketName, String key) {...}

2) 应用所有细粒度的 S3 函数来获取 S3ObjectInputStream 对象。
我们可以将所有这些收集到类 AmazonS3Facade 的特定方法中:

S3ObjectInputStream s3is = amazonS3Facade.getObjectContent(clientRegion, bucketName, key);

3) 执行处理返回的 S3ObjectInputStream 并返回结果的逻辑

json = returnStringFromInputStream(s3is); 
// ...   
return result;

现在如何测试?

很简单。
使用 JUnit 5:

@ExtendWith(MockitoExtension.class)
public MyAwsClientTest{

    MyAwsClient myAwsClient;

    @Mock 
    AmazonS3Facade amazonS3FacadeMock;        

    @Before
    void before(){
        myAwsClient = new MyAwsClient(amazonS3FacadeMock);
    }

    @Test
    void amazonS3Read(){

        // given
        String clientRegion = "us-east-1";
        String bucketName = "version";
        String key = "version.txt";

       S3ObjectInputStream s3IsFromMock = ... // provide a stream with a real content. We rely on it to perform the assertion
       Mockito.when(amazonS3FacadeMock.getObjectContent(clientRegion, bucketName, key))
              .thenReturn(s3IsFromMock);

       // when    
       Result result = myAwsClient.amazonS3Read(clientRegion, bucketName, key);

      // assert result content.
      Assertions.assertEquals(...);
    }
}

有什么优势?

  • 类实现是可读和可维护的,因为它专注于您的功能处理。
  • 整个 S3 逻辑被移到一个地方 AmazonS3Facade(单一职责原则/模块化)。
  • 因此,测试实现现在是可读和可维护的
  • 测试真正测试您执行的逻辑(而不是验证对多个模拟的一系列调用)。

请注意,单一测试 AmazonS3Facade 几乎没有值(value),因为它只是对 S3 组件的一系列调用,无法根据返回结果进行断言,因此非常脆弱。
但是,正如前面引用的那样,使用简单轻量级的 S3 兼容服务器为此编写集成测试确实很有意义。

关于java - 如何模拟 - 从 s3 读取文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57113037/

相关文章:

amazon-web-services - 即使 Lambda 返回错误,AWS API Gateway 也会返回 200

unit-testing - 如何让 NUnit 测试列表中的所有值,而不仅仅是第一个失败

xcode - 快速 Xcode 单元测试 : Cannot convert value of type "Person" to expected argument type "Person"

java - ListView 项目中的按钮

java - 为什么不能在 android 中用工具栏覆盖操作栏?

amazon-web-services - 无法使用 CloudFormation 创建 PostgreSQL,但可以使用 Web 界面

amazon-web-services - AWS DynamoDB 流到 Redshift

java - 在 JScrollPane 内的 JPanel 上绘图

java - 身份验证重定向后如何返回 Jersey MVC Viewable?

Python 单元测试 - 设置警告 : ResourceWarning