如何使用Mockito模拟AWS S3类?附代码示例寻求指导
用Mockito模拟AWS S3相关类的实现方案
首先得指出,你当前的方法直接在内部创建AmazonS3客户端,这种写法没法直接用Mockito模拟——因为客户端是方法内硬编码生成的,测试代码没法替换它。所以第一步得先重构代码,把S3客户端改成依赖注入的方式。
1. 重构原方法,注入S3客户端
把客户端通过构造方法传入,让类依赖抽象而不是自己实例化依赖:
import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.model.ObjectMetadata; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class S3FileHandler { private static final Logger log = LoggerFactory.getLogger(S3FileHandler.class); private final AmazonS3 s3Client; private String surfaceName; // 构造方法注入AmazonS3客户端 public S3FileHandler(AmazonS3 s3Client) { this.s3Client = s3Client; } public File saveFileFromS3toDisk(String bucketName, String key) throws IOException { log.info("Downloading file {} from S3.", key); S3Object fullObject = s3Client.getObject(new GetObjectRequest(bucketName, key)); ObjectMetadata metadata = fullObject.getObjectMetadata(); surfaceName = metadata.getUserMetadata().get("surface"); // 补充原方法缺失的文件写入逻辑(你原代码里没写全,这里补个示例) File tempFile = File.createTempFile("s3-download-", ".tmp"); try (InputStream in = fullObject.getObjectContent()) { Files.copy(in, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); } return tempFile; } // 给测试用的getter,用来验证surfaceName是否正确获取 public String getSurfaceName() { return surfaceName; } }
2. 编写Mockito测试用例
现在就可以用Mockito模拟所有AWS相关类了,包括AmazonS3、S3Object和ObjectMetadata:
import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.model.ObjectMetadata; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) public class S3FileHandlerTest { @Mock private AmazonS3 s3Client; @Mock private S3Object s3Object; @Mock private ObjectMetadata objectMetadata; @InjectMocks private S3FileHandler s3FileHandler; @Test void saveFileFromS3toDisk_ShouldFetchSurfaceMetadataCorrectly() throws IOException { // 准备测试数据 String testBucket = "my-test-bucket"; String testKey = "documents/report.pdf"; String expectedSurface = "mobile-app"; Map<String, String> userMetadata = new HashMap<>(); userMetadata.put("surface", expectedSurface); // 模拟整个调用链 when(objectMetadata.getUserMetadata()).thenReturn(userMetadata); when(s3Object.getObjectMetadata()).thenReturn(objectMetadata); // 模拟S3对象的输入流,避免后续写入文件时抛空指针 when(s3Object.getObjectContent()).thenReturn(new ByteArrayInputStream("dummy content".getBytes())); when(s3Client.getObject(any(GetObjectRequest.class))).thenReturn(s3Object); // 执行目标方法 File downloadedFile = s3FileHandler.saveFileFromS3toDisk(testBucket, testKey); // 验证结果 assertEquals(expectedSurface, s3FileHandler.getSurfaceName()); // 确认S3客户端的getObject方法被调用了一次 verify(s3Client, times(1)).getObject(any(GetObjectRequest.class)); // 验证文件是否成功创建 assertTrue(downloadedFile.exists()); downloadedFile.deleteOnExit(); } }
3. 核心注意点
- 依赖注入是前提:如果不在方法外注入S3客户端,测试代码根本没法替换掉内部生成的真实客户端,这是Mockito模拟的基础。
- 嵌套对象要逐层模拟:因为要从
S3Object里拿元数据,所以得依次模拟AmazonS3.getObject()返回模拟的S3Object,S3Object.getObjectMetadata()返回模拟的ObjectMetadata,最后让元数据返回预设的用户自定义Map。 - 输入流必须模拟:如果不模拟
S3Object.getObjectContent(),后续写入文件时会因为输入流为空抛出IO异常,所以返回一个空的或者测试用的字节输入流就行。 - 验证调用行为:用
verify方法可以确认S3客户端的方法确实按预期被调用了,避免漏调用或者多调用的情况。
内容的提问来源于stack exchange,提问作者Vinod Jayachandran
相关产品推荐
相关产品推荐

