当我一起使用webAppContextSetup时,我很难让Mockito和MockMvc一起工作。我很好奇是不是因为我正在以一种他们从未想过的方式混合两者。
这是我正在运行的测试:
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调用的模拟时,它会使用我在测试中定义的PersonController。但是当我调试时,它正在使用由SpringJunit4ClassRunner在测试启动时创建的PersonController。
我找到了两种方法让它发挥作用:
以下是我发现的一些不同的文章,有点涉及这个主题:
思想?
答案 0 :(得分:5)
您可能对Spring Boot 1.4中的new testing features感兴趣(特别是新的@MockBean
注释)。这个sample显示了如何模拟服务并将其与控制器测试一起使用。
答案 1 :(得分:2)
出于某种原因,Mockito注释@Mock
et @InjectMocks
在这种情况下不起作用。
以下是我设法使其发挥作用的方式:
personService
bean personService
创建模拟。PersonController
中注入这些模拟。你应该有你的TestConfig:
@Configuration
public class ControllerTestConfig {
@Bean
PersonService personService() {
return mock(PersonService.class);
}
}
在PersonControllerTest
中,您不再需要personController
,因为它由mockMvc
通过perform
方法管理。您也不需要执行initMocks()
,因为您在Spring配置中手动初始化您的模拟。你应该有类似的东西:
@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)));
}
}
答案 2 :(得分:0)
我有时会使用Mockito伪造使用@Primary
和@Profile
注释的Spring bean。 I wrote a blog post about this technique.它还包含在GitHub上托管的完整工作示例的链接。
答案 3 :(得分:0)
为扩展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模拟被包装在AopProxy中,导致Mockito的expect
和verify
调用失败。所以我为unwrap them写了一个实用方法:
@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;
}