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

如何使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 06:28:01