如何Mock读取S3文件?单元测试遇两类异常求解决
分析你的两个异常及解决方案
咱们一步步拆解你遇到的问题,然后给出修复方案:
第一个异常:Argument passed to when() is not a mock!
这个错误出在测试代码的这一行:
Mockito.doNothing().when(AmazonS3ClientBuilder.standard());
AmazonS3ClientBuilder.standard()返回的是真实的Builder实例,不是Mockito创建的mock对象。Mockito的when()方法只能用来 stub mock对象的方法调用,不能用在真实类的实例上,所以抛出这个异常。
更关键的是,你后面又调用了AmazonS3ClientBuilder.build()创建了真实的S3客户端,这就导致测试中实际使用的是真实客户端而非你mock的client变量,后面的stub逻辑完全没生效,还会触发真实的AWS请求,进而引发第二个异常。
第二个异常:profile file cannot be null
这个错误是因为真实的ProfileCredentialsProvider需要读取本地的AWS凭证文件(通常在~/.aws/credentials),而你的测试环境中没有这个文件,所以抛出了参数为空的异常。而且测试场景下,我们根本不应该使用真实的凭证提供者——测试应该和外部服务(比如AWS S3)解耦。
核心修复思路:依赖注入
解决问题的关键是把S3客户端从方法内部创建改成外部注入,这样测试时可以传入mock的客户端,完全避免创建真实客户端和触发真实请求。
第一步:重构业务代码
把AmazonS3客户端作为类的依赖,通过构造函数注入,而不是在amazonS3Read方法内部创建:
public class S3JsonProcessor { private final AmazonS3 s3Client; private String jsonContent; // 构造函数注入S3客户端,方便测试时传入mock public S3JsonProcessor(AmazonS3 s3Client) { this.s3Client = s3Client; } public void readJsonFromS3() { String bucketName = "version"; String key = "version.txt"; // 使用try-with-resources自动关闭流,避免手动关闭的遗漏 try (S3Object fullObject = s3Client.getObject(new GetObjectRequest(bucketName, key)); S3ObjectInputStream s3is = fullObject.getObjectContent()) { jsonContent = convertInputStreamToString(s3is); // 这里处理JSON数据的逻辑 } catch (AmazonServiceException e) { e.printStackTrace(); } catch (SdkClientException | IOException e) { e.printStackTrace(); } } // 工具方法:InputStream转String private String convertInputStreamToString(InputStream is) throws IOException { StringBuilder sb = new StringBuilder(); try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) { String line; while ((line = br.readLine()) != null) { sb.append(line); } } return sb.toString(); } // 提供getter方便测试验证结果 public String getJsonContent() { return jsonContent; } }
第二步:修改测试代码
现在可以完全用Mockito来mock S3客户端和相关对象,模拟S3文件的内容,不需要依赖真实的AWS服务:
import org.junit.Test; import org.mockito.Mockito; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.S3Object; import java.io.ByteArrayInputStream; import java.io.InputStream; import static org.junit.Assert.assertEquals; public class S3JsonProcessorTest { @Test public void testReadJsonFromS3() throws Exception { // 1. 创建所有需要的mock对象 AmazonS3 mockS3Client = Mockito.mock(AmazonS3.class); S3Object mockS3Object = Mockito.mock(S3Object.class); // 2. 准备测试用的JSON数据,转成InputStream模拟S3文件内容 String testJson = "{\"appVersion\": \"2.1.0\"}"; InputStream mockInputStream = new ByteArrayInputStream(testJson.getBytes()); // 3. Stub方法调用:当调用mockS3Client.getObject时返回mockS3Object GetObjectRequest request = new GetObjectRequest("version", "version.txt"); Mockito.when(mockS3Client.getObject(request)).thenReturn(mockS3Object); // 4. Stub mockS3Object的getObjectContent方法,返回我们的测试流 Mockito.when(mockS3Object.getObjectContent()).thenReturn(mockInputStream); // 5. 创建业务类实例,传入mock的S3客户端 S3JsonProcessor processor = new S3JsonProcessor(mockS3Client); // 6. 执行测试方法 processor.readJsonFromS3(); // 7. 验证结果和方法调用 assertEquals(testJson, processor.getJsonContent()); // 验证mock对象的方法是否按预期被调用 Mockito.verify(mockS3Client).getObject(request); Mockito.verify(mockS3Object).getObjectContent(); } }
为什么这样修复有效?
- 解耦外部依赖:通过依赖注入,业务代码不再直接创建S3客户端,测试时可以完全控制客户端的行为,不需要连接真实的AWS服务。
- 正确使用Mockito:所有的
when()调用都作用在Mockito创建的mock对象上,符合框架的使用规则,不会再出现"Argument passed to when() is not a mock!"异常。 - 避免凭证问题:测试中使用mock客户端,根本不会触发真实的AWS请求,也就不需要AWS凭证文件,解决了
profile file cannot be null的问题。 - 更稳定的测试:测试不依赖外部服务和网络,执行速度更快,结果更可靠。
内容的提问来源于stack exchange,提问作者gireesh_bh
相关产品推荐
相关产品推荐

