Mockito应该在Spring 4中与MockMvc的webAppContextSetup一起使用吗?



当我一起使用webAppContextSetup时,我很难让Mockito和MockMvc一起工作。我很好奇,这是否是因为我以一种从未想过的方式将两者混合在一起。

来源:https://github.com/zgardner/spring-boot-intro/blob/master/src/test/java/com/zgardner/springBootIntro/controller/PersonControllerTest.java

这是我正在运行的测试:

package com.zgardner.springBootIntro.controller;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static java.lang.Math.toIntExact;
import static org.hamcrest.Matchers.is;
import static org.mockito.MockitoAnnotations.initMocks;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
import com.zgardner.springBootIntro.Application;
import com.zgardner.springBootIntro.service.PersonService;
import com.zgardner.springBootIntro.model.PersonModel;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public class PersonControllerTest {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private DefaultListableBeanFactory beanFactory;
@Mock
private PersonService personService;
@InjectMocks
private PersonController personController;
@Before
public void setup() {
initMocks(this);
// beanFactory.destroySingleton("personController");
// beanFactory.registerSingleton("personController", personController);
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Test
public void getPersonById() throws Exception {
Long id = 999L;
String name = "Person name";
when(personService.findById(id)).thenReturn(new PersonModel(id, name));
mockMvc.perform(get("/person/getPersonById/" + id))
.andDo(print())
.andExpect(jsonPath("$.id", is(toIntExact(id))))
.andExpect(jsonPath("$.name", is(name)));
}
}

我原以为mockMvc在执行HTTP调用的mock时,会使用我在测试中定义的PersonController。但当我调试完成时,它使用的是由SpringJunit4ClassRunner在测试启动时创建的PersonController。

我找到了两种方法来实现这一点:

  1. 注入bean工厂,删除旧的personController singleton,并添加我自己的。这太难看了,我不是粉丝
  2. 使用standaloneSetup而不是webAppContextSetup连接所有内容。我可以这样做,因为我不必碰豆厂

以下是我发现的一些不同的文章,它们在某种程度上触及了这个主题:

  • Spring教程-构建REST服务这只是在进行集成测试之前自动连接repo以清除数据
  • 使用SpringMVC测试框架和Mockito来测试控制器这使用了Mockito和webAppContextSetup,但这是在Spring3中。(我使用的是Spring Boot)
  • 无法在Spring MVC Controller测试中模拟Service类这使用了standaloneSetup,它在我的情况下也能工作

想法?

您可能对Spring Boot 1.4中的新测试功能(特别是新的@MockBean注释)感兴趣。此示例显示了如何模拟服务并将其与控制器测试一起使用。

由于某些原因,Mockito注释@Mock@InjectMocks在这种情况下不起作用。

以下是我如何使其发挥作用:

  • 使用自己的测试上下文手动实例化personServicebean
  • 让Mockito为这个personService创建一个mock
  • 让Spring在控制器PersonController中注入这些mock

您应该有您的TestConfig:

@Configuration
public class ControllerTestConfig {
@Bean
PersonService personService() {
return mock(PersonService.class);
}
}

PersonControllerTest中,您将不再需要personController,因为它是由mockMvc通过perform方法管理的。您也不需要执行initMocks(),因为您可以在Spring配置中手动初始化mock。你应该有这样的东西:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class, ControllerTestConfig.class})
@WebAppConfiguration
public class PersonControllerTest {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
PersonService personService;
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Test
public void getPersonById() throws Exception {
Long id = 999L;
String name = "Person name";
when(personService.findById(id)).thenReturn(new PersonModel(id, name));
mockMvc.perform(get("/person/getPersonById/" + id))
.andDo(print())
.andExpect(jsonPath("$.id", is(toIntExact(id))))
.andExpect(jsonPath("$.name", is(name)));
}
}

我有时会使用Mockito通过使用@Primary@Profile注释来伪造Springbean。我写了一篇关于这项技术的博客文章。它还包含GitHub上托管的完整工作示例的链接。

为了扩展florent的解决方案,我遇到了性能问题和可扩展性问题,为每个需要不同服务模拟集的控制器测试创建了单独的配置。因此,我能够通过在测试的同时实现BeanPostProcessor来模拟我的应用程序的服务层,该测试将所有@Service类替换为模拟:

@Component
@Profile("mockService")
public class AbcServiceMocker implements BeanPostProcessor {
private static final String ABC_PACKAGE = "com.mycompany.abc";
@Override
public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
if (StringUtils.startsWith(bean.getClass().getPackage().getName(), ABC_PACKAGE)) {
if (AnnotationUtils.isAnnotationDeclaredLocally(Service.class, bean.getClass())
|| AnnotationUtils.isAnnotationInherited(Service.class, bean.getClass())) {
return mock(bean.getClass());
}
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String name) throws BeansException {
return bean;
}
}

我在特定测试中使用@ActiveProfiles注释启用了这些模拟:

@WebAppConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:/WEB-INF/application-context.xml"})
@ActiveProfiles("mockService")
public class AbcControllerTest {
private MockMvc mvc;
@Before
public final void testBaseSetup() {
mvc = MockMvcBuilders.webAppContextSetup(context).build();
}

最后,注入的Mockito mock被封装在AopProxy中,导致Mockito的expectverify调用失败。所以我写了一个实用的方法来打开它们:

@SuppressWarnings("unchecked")
protected <T> T mockBean(Class<T> requiredType) {
T s = context.getBean(requiredType);
if (AopUtils.isAopProxy(s) && s instanceof Advised) {
TargetSource targetSource = ((Advised) s).getTargetSource();
try {
return (T) targetSource.getTarget();
} catch (Exception e) {
throw new RuntimeException("Error resolving target", e);
}
}
Mockito.reset(s);
return s;
}

相关内容

  • 没有找到相关文章

最新更新