如何在@WebMvcTest 测试中忽略@EnableWebSecurity 注释类
Posted
技术标签:
【中文标题】如何在@WebMvcTest 测试中忽略@EnableWebSecurity 注释类【英文标题】:How to ignore @EnableWebSecurity annotated class in @WebMvcTest tests 【发布时间】:2020-04-29 11:29:30 【问题描述】:在下面的测试类中,我不希望 @EnableWebSecurity 注解的类被 Spring Context 捕获:
@WebMvcTest(controllers = UserController.class)
class UserControllerTest
@MockBean
private UserService userService;
@Autowired
private ObjectMapper jsonMapper;
@Autowired
private MockMvc mockMvc;
@Test
void create_should_return_registered_user_when_request_is_valid() throws Exception
// given
final String EMAIL = "test@test.com";
final String PASSWORD = "test_password";
final UserDto userDto = buildDto(EMAIL, PASSWORD);
final User expectedUser = buildUser(EMAIL, PASSWORD);
// when
when(userService.registerUser(userDto)).thenReturn(expectedUser);
// then
MvcResult response = mockMvc.perform(post(UserAPI.BASE_URL)
.contentType(MediaType.APPLICATION_JSON)
.content(jsonMapper.writeValueAsString(userDto)))
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andReturn();
String responseBodyJson = response.getResponse().getContentAsString();
User responseUser = jsonMapper.readValue(responseBodyJson, User.class);
assertThat(responseUser, is(equalTo(expectedUser)));
verify(userService, times(1)).registerUser(userDto);
verifyNoMoreInteractions(userService);
@Test
void create_should_return_conflict_when_request_valid_but_email_in_use() throws Exception
// given
final String EMAIL = "test@test.com";
final String PASSWORD = "test_password";
final UserDto userDto = buildDto(EMAIL, PASSWORD);
// when
when(userService.registerUser(userDto)).thenThrow(new EmailAlreadyInUseException(EMAIL));
// then
mockMvc.perform(post(UserAPI.BASE_URL)
.contentType(MediaType.APPLICATION_JSON)
.content(jsonMapper.writeValueAsString(userDto)))
.andExpect(status().isConflict());
verify(userService, times(1)).registerUser(userDto);
verifyNoMoreInteractions(userService);
@Test
void create_should_return_bad_request_when_request_has_invalid_email() throws Exception
// given
final String BAD_EMAIL = "test_test.com";
final String PASSWORD = "test_password";
final UserDto userDto = buildDto(BAD_EMAIL, PASSWORD);
// when
// then
mockMvc.perform(post(UserAPI.BASE_URL)
.contentType(MediaType.APPLICATION_JSON)
.content(jsonMapper.writeValueAsString(userDto)))
.andExpect(status().isBadRequest());
verifyNoInteractions(userService);
@Test
void create_should_return_bad_request_when_request_has_invalid_password() throws Exception
// given
final String EMAIL = "test@test.com";
final String BAD_PASSWORD = "";
final UserDto userDto = buildDto(EMAIL, BAD_PASSWORD);
// when
// then
mockMvc.perform(post(UserAPI.BASE_URL)
.contentType(MediaType.APPLICATION_JSON)
.content(jsonMapper.writeValueAsString(userDto)))
.andExpect(status().isBadRequest());
verifyNoInteractions(userService);
@Test
void create_should_return_bad_request_when_request_is_missing_email() throws Exception
// given
final String PASSWORD = "test_password";
final UserDto userDto = buildDto(null, PASSWORD);
// when
// then
mockMvc.perform(post(UserAPI.BASE_URL)
.contentType(MediaType.APPLICATION_JSON)
.content(jsonMapper.writeValueAsString(userDto)))
.andExpect(status().isBadRequest());
verifyNoInteractions(userService);
@Test
void create_should_return_bad_request_when_request_is_missing_password() throws Exception
// given
final String EMAIL = "test@test.com";
final UserDto userDto = buildDto(EMAIL, null);
// when
// then
mockMvc.perform(post(UserAPI.BASE_URL)
.contentType(MediaType.APPLICATION_JSON)
.content(jsonMapper.writeValueAsString(userDto)))
.andExpect(status().isBadRequest());
verifyNoInteractions(userService);
private UserDto buildDto(String email, String password)
UserDto userDto = new UserDto();
userDto.setEmail(email);
userDto.setPassword(password);
return userDto;
private User buildUser(String email, String password)
User user = new User();
user.setId(1);
user.setEmail(email);
user.setPassword(password);
return user;
现在默认加载,因为它的依赖没有加载,所以报错:
com.example.ordersapi.auth.configuration.SecurityConfiguration 中构造函数的参数 0 需要一个无法找到的 'org.springframework.security.core.userdetails.UserDetailsService' 类型的 bean。
我见过一些解决方案,例如 @WebMvcTest(controllers = SomeController.class, secure = false)
,但这些似乎已被弃用。
我正在运行 Spring Boot v2.2.2.RELEASE。
这是安全配置类:
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
@Value("$spring.h2.console.enabled:false")
private boolean h2ConsoleEnabled;
private final UserDetailsService userDetailsService;
private final AuthorizationFilter authorizationFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
@Override
protected void configure(HttpSecurity http) throws Exception
if (h2ConsoleEnabled)
http.authorizeRequests()
.antMatchers("/h2-console", "/h2-console/**").permitAll()
.and()
.headers().frameOptions().sameOrigin();
http.cors().and().csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(unauthorizedHandler())
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, AuthenticationAPI.BASE_URL).permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authorizationFilter, UsernamePasswordAuthenticationFilter.class);
private AuthenticationEntryPoint unauthorizedHandler()
return (request, response, e) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
/**
* We have to create this bean otherwise we can't wire AuthenticationManager in our code.
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
return super.authenticationManagerBean();
@Bean
public PasswordEncoder passwordEncoder()
return new BCryptPasswordEncoder();
【问题讨论】:
你能分享你的 com.example.ordersapi.auth.configuration.SecurityConfiguration 吗?谢谢 已添加到问题中。谢谢! 您是否创建了自己的UserDetailsService
实现?如果有,请提供。你很接近,没有错过太多。
嗨,我确实尝试过提供它,它也是依赖项,但之后我遇到了另一个问题:spring security is up,显然没有使用我的配置/服务,即使它们被加载到上下文,当我尝试使用 @WithMockService 时,我仍然得到 403 Forbidden 作为响应状态
它应该是……奇怪。请分享它,以便我可以尝试复制...谢谢
【参考方案1】:
可以使用@Configuration 和@EnableWebSecurity 属性覆盖安全配置。因为您没有使用@TestConfiguration,所以您可能需要使用@Import 导入类,如下所示。我喜欢这个解决方案,而不是扫描你的主机包中的 bean,因为我觉得你可以更好地控制框架正在加载的内容。
@RunWith(SpringRunner.class)
@WebMvcTest(controllers = MyController.class)
@Import(MyController.class)
public class MyControlleTests
@Autowired
private MockMvc mvc;
@MockBean
private SomeDependency someDependencyNeeded;
@Configuration
@EnableWebSecurity
static class SecurityConfig extends WebSecurityConfigurerAdapter
@Override
protected void configure(HttpSecurity http) throws Exception
http
.csrf().disable()
.authorizeRequests().anyRequest().anonymous();
@Test
public void some_route_returns_ok() throws Exception
MockHttpServletRequestBuilder requestBuilder =
MockMvcRequestBuilders.get("mycontrolleraction");
mvc
.perform(requestBuilder)
.andExpect(MockMvcResultMatchers.status().isOk());
请注意,有人可能会争辩说您应该只将安全性作为测试的一部分;但是,我的意见是,您应该尽可能隔离地测试架构的每个组件。
【讨论】:
【参考方案2】:在将 Spring Boot 从 2.1.x 迁移到 2.2.x 后,我也遇到了同样的问题,即 401 代码。此后,secure
字段从@WebMvcTest
注释中移除。
我通过添加忽略过滤器(包括身份验证过滤器)的注释来修复:
@WebMvcTest(value = SomeResource.class)
@AutoConfigureMockMvc(addFilters = false)
class SomeTest
【讨论】:
【参考方案3】:我找到的最简单的解决方案是在您的 SecurityConfiguration 类中添加 @Profile(!test)
。这应该完全防止在测试期间加载类。
默认情况下,测试使用测试配置文件运行,如果您要覆盖您可能必须放入您正在使用的配置文件。 (日志显示上下文启动时哪个配置文件处于活动状态)。
有关个人资料的更多信息:https://www.baeldung.com/spring-profiles。
您也可以使用@WithMockUser(roles = "MANAGER")
。有关更多信息,请参阅此问题:Spring Test & Security: How to mock authentication?
【讨论】:
嗨!感谢您的回答我还尝试为我的配置和所有其他安全 bean 提供另一个配置文件,它解决了当前的问题,但即使仍然会设置网络安全(使用默认值),当我尝试使用 @WithMockUser 我仍然得到 403 Forbbiden MvcResult response = mockMvc.perform(post(UserAPI.BASE_URL).with(csrf())... 让它通过。这很糟糕,因为我不希望安全问题污染这些测试套房 仅供参考,解决方案在这里:***.com/questions/59776298/…以上是关于如何在@WebMvcTest 测试中忽略@EnableWebSecurity 注释类的主要内容,如果未能解决你的问题,请参考以下文章
如何将 @WebMvcTest 用于单元测试 POST 方法?
在 Spring Boot 1.4 MVC 测试中使用 @WebMvcTest 设置 MockMvc
使用 @WebMvcTest 测试中的 ApplicationContext 异常
在 Spring Boot 测试类上使用 @WebMvcTest 注释时出错
@WebMvcTest 在 Spring Boot 测试中为不同服务提供“Error Creating bean with name”错误