Java单元测试中调用依赖Bean实例的工具类Mock方案咨询
单元测试Mock静态依赖解决方案
问题核心原因
你当前测试逻辑失效的核心问题有两个:
- 普通
Mockito框架不支持Mock静态方法,你编写的Mockito.when(OAuth2StringUtils.getTokenRedirectURL())会直接执行真实的静态方法逻辑,触发OAuth2CoreConfigProperties.getInstance()调用,拿不到实例会抛出空指针异常 - 你预先构造的
HttpEntity参数和业务代码实际生成的参数不一致,restTemplate.exchange的条件匹配失败,会返回空值
方案1:使用PowerMock扩展Mockito能力(无需修改业务代码)
PowerMock可以支持Mock静态方法、私有方法、单例类等特殊场景,适合不方便改动业务代码的情况:
第一步:引入PowerMock依赖
以JUnit4为例,添加对应依赖:
<!-- PowerMock 核心依赖 --> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito2</artifactId> <version>2.0.9</version> <scope>test</scope> </dependency>
第二步:修改测试类注解
测试类上增加PowerMock相关配置:
@RunWith(PowerMockRunner.class) @PrepareForTest({OAuth2StringUtils.class, OAuth2CoreConfigProperties.class}) public class WebClientServiceTest { // 原有Spy、Mock、InjectMocks注解保持不变 }
第三步:修正Mock逻辑
调整测试用例的Mock代码:
@Test void should_return_token_given_auth_code_when_get_token() throws Exception { // given String authCode = "authCode"; String accessToken = "accessToken"; TokenDTO tokenDTO = TokenDTO.builder().access_token(accessToken).build(); ResponseEntity<TokenDTO> response = ResponseEntity.ok(tokenDTO); // Mock静态工具类返回值 PowerMockito.mockStatic(OAuth2StringUtils.class); Mockito.when(OAuth2StringUtils.getTokenRedirectURL()).thenReturn("token_redirect_url"); // 调整restTemplate匹配规则,用any匹配HttpEntity,避免参数不一致匹配失败 Mockito.when(restTemplate.exchange( Mockito.eq(config.getTokenEndpoint()), Mockito.eq(HttpMethod.POST), Mockito.any(HttpEntity.class), Mockito.eq(TokenDTO.class) )).thenReturn(response); // then TokenDTO result = webClientService.getToken(authCode); assertEquals(accessToken, result.getAccess_token()); }
方案2:重构业务代码解耦静态依赖(更推荐)
硬编码的静态方法+单例调用不符合Spring依赖注入的设计规范,会降低代码可扩展性和可测试性,建议重构:
重构步骤:
- 移除
OAuth2StringUtils的静态方法,将其改为普通Spring Bean,通过依赖注入获取OAuth2CoreConfigProperties实例:
@Component public class OAuth2StringUtils { @Autowired private OAuth2CoreConfigProperties configProperties; public String getTokenRedirectURL() { return configProperties.getTokenRedirectUrl(); } }
- 在
WebClientServiceImpl中注入OAuth2StringUtils实例,替换原有静态调用:
@Service public class WebClientServiceImpl implements WebClientService { @Autowired private OAuth2StringUtils oAuth2StringUtils; @Override public TokenDTO getToken(String authCode) { HttpEntity<MultiValueMap<String, String>> entity = generateCommonHttpEntity(); entity.getBody().add("grant_type", OAuth2Constants.GRANT_TYPE_AUTHORIZATION_CODE); // 替换为实例方法调用 entity.getBody().add("redirect_uri", oAuth2StringUtils.getTokenRedirectURL()); entity.getBody().add("code",authCode); return getTokenByPOST(entity); } }
重构后的测试逻辑
不需要引入PowerMock,直接用普通Mockito即可完成Mock:
// JUnit5用@ExtendWith(MockitoExtension.class),JUnit4用@RunWith(MockitoJUnitRunner.class) @ExtendWith(MockitoExtension.class) public class WebClientServiceTest { @Spy @InjectMocks WebClientServiceImpl webClientService; @Mock OAuth2CoreConfigProperties config; @Mock RestTemplate restTemplate; @Mock OAuth2StringUtils oAuth2StringUtils; // 直接Mock实例 @Test void should_return_token_given_auth_code_when_get_token() throws Exception { String authCode = "authCode"; String accessToken = "accessToken"; TokenDTO tokenDTO = TokenDTO.builder().access_token(accessToken).build(); ResponseEntity<TokenDTO> response = ResponseEntity.ok(tokenDTO); // Mock普通实例方法即可 Mockito.when(oAuth2StringUtils.getTokenRedirectURL()).thenReturn("token_redirect_url"); Mockito.when(restTemplate.exchange( Mockito.eq(config.getTokenEndpoint()), Mockito.eq(HttpMethod.POST), Mockito.any(HttpEntity.class), Mockito.eq(TokenDTO.class) )).thenReturn(response); TokenDTO result = webClientService.getToken(authCode); assertEquals(accessToken, result.getAccess_token()); } }
内容的提问来源于stack exchange,提问作者Gentle Chen
相关产品推荐
相关产品推荐

