Java单元测试无法Mock AmazonS3实例 调用真实方法报403错误
单元测试Mock AmazonS3仍发起真实请求报403的解决方法
问题表现
业务代码中通过内部方法构建AmazonS3客户端调用listObjectsV2接口,测试时已编写Mock配置,但执行到S3调用逻辑时抛出真实请求的权限错误:
Access Denied (Service: Amazon S3; Status Code: 403; Error Code: AccessDenied;
涉及的业务代码(Processor类):
AmazonS3 s3 = getS3Build(); final List<String> flagFiles = s3.listObjectsV2(dataBucket, flagFolder); public AmazonS3 getS3Build() { return AmazonS3ClientBuilder.standard().withRegion(US_EAST_1).build(); }
原有测试Mock配置:
Mockito.when(processor.getS3Build()).thenReturn(s3); Mockito.when(s3.listObjectsV2(anyString(), anyString())).thenReturn(listObjectsV2Result);
根因分析
出现这个问题基本是两个原因导致:
- 被测试的
processor是普通真实实例,没有声明为Spy对象,执行Mockito.when(processor.getS3Build())时会先真实调用getS3Build()方法,生成可发起真实请求的S3客户端,后续业务逻辑拿到真实客户端就会发起实际S3请求。 - 打桩Spy对象方法时用了
when().thenReturn()语法,该语法会先执行一次真实方法,同样会生成真实S3客户端导致Mock失效。部分低版本AWS SDK的S3客户端实现类带final修饰,默认Mockito配置无法Mock final类/方法,会直接走真实实现。
正确实现方案
方案1:修正现有Mock配置(最小改动)
- 测试类先开启Mockito注解支持:JUnit4环境在测试类上加
@RunWith(MockitoJUnitRunner.class),JUnit5环境加@ExtendWith(MockitoExtension.class)。 - 把被测试的
processor声明为@Spy+@InjectMocks,AmazonS3实例声明为@Mock。 - 打桩
getS3Build()方法时改用doReturn().when()语法,避免触发真实方法调用。
完整测试代码示例:
@ExtendWith(MockitoExtension.class) // JUnit4换@RunWith(MockitoJUnitRunner.class) public class ProcessorTest { @Mock private AmazonS3 s3; @Mock private ListObjectsV2Result listObjectsV2Result; @Spy @InjectMocks private Processor processor; @Test public void testS3ListLogic() { // 注意这里必须用doReturn打桩spy的方法,不要用when().thenReturn doReturn(s3).when(processor).getS3Build(); when(s3.listObjectsV2(anyString(), anyString())).thenReturn(listObjectsV2Result); // 执行业务测试逻辑 processor.executeYourBusinessMethod(); // 验证交互、断言结果 verify(s3, times(1)).listObjectsV2(anyString(), anyString()); } }
如果是SDK版本导致final类无法Mock,升级Mockito到3.4.0以上版本,在src/test/resources/mockito-extensions路径下创建名为org.mockito.plugins.MockMaker的文件,文件内容填写mock-maker-inline,即可开启final类/方法的Mock支持。
方案2:重构代码优化可测试性(推荐)
不要在业务类内部直接通过Builder构建S3客户端,改为构造器注入AmazonS3实例,从根源上避免Mock内部构建方法的问题,更符合依赖注入规范:
重构后的Processor类:
private final AmazonS3 s3; // 构造器注入S3客户端,生产环境传入真实客户端,测试时传入Mock对象 public Processor(AmazonS3 s3) { this.s3 = s3; } public void executeYourBusinessMethod() { // 直接使用注入的s3实例,不需要单独的getS3Build方法 final List<String> flagFiles = s3.listObjectsV2(dataBucket, flagFolder); // 后续业务逻辑 }
重构后测试时直接把Mock好的AmazonS3实例传入Processor构造器即可,不需要额外打桩内部构建方法,稳定性更高。
内容的提问来源于stack exchange,提问作者Vishal Sain
相关产品推荐
相关产品推荐

