如何针对已认证与未认证用户重复执行Spring Security测试?
实现OAuth2接口的双场景自动测试(认证/未认证)
针对你的Spring Security OAuth2接口测试需求,以下两种方案可以让测试自动执行已认证和未认证两个场景:
方案一:JUnit 5参数化测试(快速实现)
利用JUnit 5的参数化测试特性,直接为每个测试方法注入认证场景参数,分场景执行断言。
1. 引入依赖(若未添加)
如果使用Maven,确保pom.xml中包含JUnit 5参数化依赖:
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <scope>test</scope> </dependency>
2. 定义认证场景枚举
enum AuthScenario { // 已认证场景 AUTHENTICATED, // 未认证场景 UNAUTHENTICATED }
3. 改造测试类
将原有测试方法改为参数化测试,根据场景设置请求并执行对应断言:
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oauth2Login; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; public class HelloControllerTests { @Autowired private MockMvc mockMvc; @ParameterizedTest @EnumSource(AuthScenario.class) public void shouldReturnWorld(AuthScenario scenario) throws Exception { var request = get("/api/hello"); // 已认证场景添加oauth2Login处理器 if (scenario == AuthScenario.AUTHENTICATED) { request = request.with(oauth2Login()); } var result = mockMvc.perform(request); // 分场景断言 if (scenario == AuthScenario.AUTHENTICATED) { result.andExpect(status().isOk()) .andExpect(content().string(containsString("Hello, World!"))); } else { result.andExpect(status().isForbidden()) .andExpect(content().string(containsString("通用错误信息"))); } } @ParameterizedTest @EnumSource(AuthScenario.class) public void shouldReturnBob(AuthScenario scenario) throws Exception { var request = get("/api/hello").param("name", "bob"); if (scenario == AuthScenario.AUTHENTICATED) { request = request.with(oauth2Login()); } var result = mockMvc.perform(request); if (scenario == AuthScenario.AUTHENTICATED) { result.andExpect(status().isOk()) .andExpect(content().string(containsString("Hello, Bob"))); } else { result.andExpect(status().isForbidden()) .andExpect(content().string(containsString("通用错误信息"))); } } }
方案二:自定义注解+测试模板(减少重复代码)
如果有大量测试方法需要复用双场景逻辑,可以自定义注解,结合JUnit 5的测试模板扩展,统一管理场景执行逻辑。
1. 自定义注解
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.junit.jupiter.api.TestTemplate; import org.junit.jupiter.api.extension.ExtendWith; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @TestTemplate @ExtendWith(AuthScenarioExtension.class) public @interface EnsureAuthenticated { }
2. 实现扩展类
这个类负责提供双场景的测试上下文,并传递场景参数:
import org.junit.jupiter.api.extension.*; import java.util.List; import java.util.stream.Stream; public class AuthScenarioExtension implements TestTemplateInvocationContextProvider { @Override public boolean supportsTestTemplate(ExtensionContext context) { return context.getTestMethod() .map(method -> method.isAnnotationPresent(EnsureAuthenticated.class)) .orElse(false); } @Override public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) { return Stream.of( createContext(AuthScenario.AUTHENTICATED), createContext(AuthScenario.UNAUTHENTICATED) ); } private TestTemplateInvocationContext createContext(AuthScenario scenario) { return new TestTemplateInvocationContext() { @Override public String getDisplayName(int invocationIndex) { return scenario.name(); } @Override public List<Extension> getAdditionalExtensions() { return List.of(new ParameterResolver() { @Override public boolean supportsParameter(ParameterContext paramCtx, ExtensionContext extCtx) { return paramCtx.getParameter().getType() == AuthScenario.class; } @Override public Object resolveParameter(ParameterContext paramCtx, ExtensionContext extCtx) { return scenario; } }); } }; } }
3. 使用自定义注解改造测试类
只需给测试方法标记@EnsureAuthenticated,即可自动执行双场景测试:
@EnsureAuthenticated public void shouldReturnWorld(AuthScenario scenario) throws Exception { // 逻辑和参数化测试中的一致,省略重复代码 } @EnsureAuthenticated public void shouldReturnBob(AuthScenario scenario) throws Exception { // 逻辑和参数化测试中的一致,省略重复代码 }
注意事项
- 未认证场景的状态码:你的过滤器链配置
/api/**需要认证,未认证访问默认返回403(Forbidden),若实际返回401(Unauthorized),需调整断言中的status().isForbidden()为status().isUnauthorized()。 - 错误信息断言:如果接口返回JSON格式错误信息,建议使用
jsonPath断言,例如andExpect(jsonPath("$.message").value("通用错误信息"))。
内容的提问来源于stack exchange,提问作者udduu
相关产品推荐
相关产品推荐

