Spring Boot 单元测试与 SpringSecurity 和 Mock



目前我有几个正常工作的单元测试。在我的单元测试中,在 init 上,我包含了以下代码:

@Mock
private UsersServices usersServices;
@InjectMocks
private UsersController usersControllers;
@Before
public void init() {
  this.mvc = MockMvcBuilders.standaloneSetup(usuariosController)
                            .addFilter(springSecurityFilterChain)
                            .setControllerAdvice(new UsuariosControllerAdvice(logService)).build();
}

这很好用,但一些授权注释(如@PreAuthorize)被忽略了。(在我的网络安全配置中,我已经添加了@EnableGlobalMethodSecurity(prePostEnabled = true)注释。

所以,过了一段时间,我找到了以下代码:

@Mock
private UsersServices usersServices;
@InjectMocks
private UsersController usersControllers;
@Autowired
private WebApplicationContext wac;
@Before
public void init() {
  this.mvc = MockMvcBuilders
      .webAppContextSetup(wac)
      .addFilter(springSecurityFilterChain)
      apply(SecurityMockMvcConfigurers.springSecurity(springSecurityFilterChain))
      .build();
}

现在,授权注释(@PreAuthorize)有效,但UsersServices模拟不起作用。当我在单元测试中调用控制器方法时,调用的是真正的UserServices,而不是模拟。

下面是一个模拟UserServices的单元测试:

when(usersServices.getUserAvatar(anyString())).thenReturn(
    CompletableFuture.completedFuture(Optional.empty()));
MvcResult result = mvc.perform(
    get("/api/users/avatar/{login:.+}", "mock.user")
        .header("Authorization", testHelpers.buildJwtToken("USER")))
        .andReturn();
    mvc.perform(asyncDispatch(result))
       .andExpect(status().isNotFound());

没有standaloneSetup,就叫真正的userServices.getUserAvatar

发生这种情况是因为您的WebApplicationContext不知道您的UsersController与模拟UsersServices。要解决此问题,您有两种选择:

第一种选择是使用

@MockBean
private UsersServices usersServices;

而不是:

@Mock
private UsersServices usersServices;

这会将模拟的 bean 添加到应用程序上下文中,以便 Spring 知道它,因此会使用它而不是真实的 bean。

第二种选择是手动直接在WebApplicationContext内设置控制器。此选项不应"在家尝试",但对于由于旧的 spring 版本而没有@MockedBean的情况,可以作为一种解决方法:

AutowireCapableBeanFactory factory = wac.getAutowireCapableBeanFactory();
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) factory;
registry.removeBeanDefinition(beanId);
//create newBeanObj through GenericBeanDefinition
registry.registerBeanDefinition(beanId, newBeanObj);

最新更新