如何使用JUnit和Mockito模拟Feign拦截器中的OAuth2AuthorizedClientManager?
JUnit + Mockito 测试Feign OAuth2拦截器
前置依赖
确保测试包中包含以下依赖:
- JUnit 5 (Jupiter)
- Mockito Core 及 Mockito JUnit Jupiter 扩展
- Spring Cloud Feign 基础依赖
- Spring Security OAuth2 相关测试依赖
测试实现
假设你的拦截器定义在FeignConfig类中,以下是完整的测试类:
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 org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.core.OAuth2AccessToken; import feign.RequestInterceptor; import feign.RequestTemplate; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.http.HttpHeaders; import static org.mockito.Mockito.*; import static org.junit.jupiter.api.Assertions.*; @ExtendWith(MockitoExtension.class) public class FeignOAuth2InterceptorTest { @Mock private OAuth2AuthorizedClientManager authorizedClientManager; @InjectMocks private FeignConfig feignConfig; @Test void interceptorAddsValidBearerTokenToRequest() { // 1. 准备测试数据 String mockToken = "sample-access-token-xyz"; Authentication mockAuth = mock(Authentication.class); OAuth2AccessToken accessToken = mock(OAuth2AccessToken.class); OAuth2AuthorizedClient authorizedClient = mock(OAuth2AuthorizedClient.class); // 设置Security上下文 SecurityContextHolder.getContext().setAuthentication(mockAuth); // 2. 配置Mock行为 when(accessToken.getTokenValue()).thenReturn(mockToken); when(authorizedClient.getAccessToken()).thenReturn(accessToken); when(authorizedClientManager.authorize(any(OAuth2AuthorizeRequest.class))) .thenReturn(authorizedClient); // 3. 执行拦截逻辑 RequestInterceptor interceptor = feignConfig.requestInterceptor(); RequestTemplate requestTemplate = new RequestTemplate(); interceptor.apply(requestTemplate); // 4. 验证结果 String authHeader = requestTemplate.headers().get(HttpHeaders.AUTHORIZATION).get(0); assertEquals("Bearer " + mockToken, authHeader); // 验证授权请求参数是否正确 verify(authorizedClientManager, times(1)) .authorize(argThat(req -> "keycloak".equals(req.getClientRegistrationId()) && mockAuth.equals(req.getPrincipal()) )); // 清理Security上下文 SecurityContextHolder.clearContext(); } @Test void interceptorThrowsExceptionWhenAuthorizedClientIsNull() { Authentication mockAuth = mock(Authentication.class); SecurityContextHolder.getContext().setAuthentication(mockAuth); // 模拟授权失败返回null when(authorizedClientManager.authorize(any(OAuth2AuthorizeRequest.class))) .thenReturn(null); RequestInterceptor interceptor = feignConfig.requestInterceptor(); RequestTemplate requestTemplate = new RequestTemplate(); // 验证空指针异常抛出 assertThrows(NullPointerException.class, () -> interceptor.apply(requestTemplate)); SecurityContextHolder.clearContext(); } }
核心测试逻辑说明
- Security上下文模拟:拦截器依赖
SecurityContextHolder获取认证信息,测试时需手动设置模拟的Authentication,测试完成后必须清理上下文,避免影响其他用例。 - Mock依赖行为:通过Mockito模拟
OAuth2AuthorizedClientManager的返回值,构造完整的授权令牌链路,确保拦截器能正确获取令牌。 - 参数校验:使用
argThat验证authorize方法接收的OAuth2AuthorizeRequest是否符合预期(客户端ID为"keycloak"、principal为当前认证对象)。 - 异常场景覆盖:测试授权失败返回
null的情况,验证拦截器的异常处理逻辑(当前实现会抛出NPE,若实际业务有容错处理,需对应调整测试)。
注意事项
- 确保
FeignConfig中的requestInterceptor()方法为public,否则测试类无法调用。 - 若
FeignConfig存在其他依赖,需通过@Mock模拟并配合@InjectMocks完成注入。 - 测试时尽量覆盖正常流程、异常流程等核心场景,保证拦截器逻辑的健壮性。
内容的提问来源于stack exchange,提问作者springLover
相关产品推荐
相关产品推荐

