java - java Mockito在使用 @Mock 时向 spring bean注入空值?

我是spring Test MVC的新手,不太明白这个问题,我从http://markchensblog.blogspot.in/search/label/Spring获得代码。

变量mockproductService不是从Application Context 注入的,使用@Mockannotation发生了assetion错误。

我遇到的错误如下:


java.lang.AssertionError: Model attribute 'Products' expected:<[com.pointel.spring.test.Product@e1b42, com.pointel.spring.test.Product@e1f03]> but was:<[]>


 at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:60)


 at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:89)


 at org.springframework.test.web.servlet.result.ModelResultMatchers$2.match(ModelResultMatchers.java:68)


 at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:141)


 at com.pointel.spring.test.ProductControllerTest.testMethod(ProductControllerTest.java:84)



注:如果我使用@Autowired而不是@Mock,它工作正常。

测试控制器类


RunWith(SpringJUnit4ClassRunner.class)


@WebAppConfiguration


@ContextConfiguration(locations={"classpath:mvc-dispatcher-servlet.xml"})


@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class})


public class ProductControllerTest {



 @Autowired


 private WebApplicationContext wac;



 private MockMvc mockMvc;



 @InjectMocks


 private ProductController productController;



 @Mock


 //@Autowired


 private ProductService mockproductService;



 @Before


 public void setup() {



 MockitoAnnotations.initMocks(this);



 List<Product> products = new ArrayList<Product>();


 Product product1 = new Product();


 product1.setId(new Long(1));



 Product product2 = new Product();


 product2.setId(new Long(2));



 products.add(product1);


 products.add(product2);



 Mockito.when(mockproductService.findAllProducts()).thenReturn(products);



 this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();



 }



 @Test


 public void testMethod() throws Exception {



 List<Product> products = new ArrayList<Product>();



 Product product1 = new Product();


 product1.setId(new Long(1));



 Product product2 = new Product();


 product2.setId(new Long(2));



 products.add(product1);


 products.add(product2);



 RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/products");



 this.mockMvc.perform(requestBuilder).


 andExpect(MockMvcResultMatchers.status().isOk())


 .andExpect(MockMvcResultMatchers.model().attribute("Products", products))


 //.andExpect(MockMvcResultMatchers.model().size(2))


 .andExpect(MockMvcResultMatchers.view().name("show_products"));



 }


}



控制器类


@Controller


public class ProductController {



 @Autowired


 private ProductService productService;



 @RequestMapping("/products")


 public String testController(ModelMap model){


 model.addAttribute("Products",productService.findAllProducts());


 return"show_products";


 }


}



WebServletContext mvc-dispatcher-servlet.xml


<bean id="someDependencyMock" class="org.mockito.Mockito" factory-method="mock">


 <constructor-arg value="com.pointel.spring.test.ProductService" />


</bean>


 <context:component-scan base-package="com.pointel.spring.test" />



<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" > 


 <property name="prefix" value="/WEB-INF/jsp/" />


 <property name="suffix" value=".jsp" />


</bean>



时间:

对于我来说,spring 和Mockito的组合是不清楚的,因为从引用的博客源中获取它应该能像预期的那样工作。 至少我可以解释你的观察:

  • 测试( this.mockMvc.perform() ) 正在处理由 spring 创建的web应用程序上下文。 在那个上下文中,ProductController 是由 spring ( context:component-scan ) 实例化的。 然后使用你在 mvc-dispatcher-servlet.xml 中创建的Mockito模拟作为 someDependencyMock 自动创建了 productService
  • 如果通过 @Autowired 注入 mockproductServicespring 从它的上下文中注入 someDependencyMock 实例。 因此 Mockito.when() 调用在这个实例上工作正常,它已经被正确地连接到前面提到的ProductController
  • 但是如果通过 @Mock 注入 mockproductService会为 productService 注入一个的新的实例,而不是 spring 上下文的一个,因为它根本不了解 spring 。 因此 Mockito.when() 调用不对由 spring 自动装配的mock操作,因此 someDependencyMock 保持未初始化状态。

所以我不清楚博客原始代码的工作原理是什么?

  • 使用 @InjectMocks 注释的ProductController 属性将由Mockito初始化,甚至正确连接到测试类中的mockproductService 。 但是 spring 对这个对象一无所知,也不会在 this.mockMvc.perform() 调用中使用它。 假设你在测试过程中只使用了 mockproductService,那么即使你在测试类中同时设置了 ProductController 属性和 MockitoAnnotations.initMocks() 调用,你的测试也就。

一般的测试规则是,不能混合不同类型的测试,第一级测试是单元测试,这意味着测试单个工作单元(通常是一个类),一旦单元测试通过,就编写集成测试,将某些组件(类)组合在一起,并测试它们如何协同工作。

类很少不依赖别的东西,因此要创建一个真正的好的单元测试,你需要模拟它的所有依赖项。


@RunWith(MockitoJUnitRunner.class)


public class ProductControllerTest {


 @Mock private ProductService mockproductService;


 @InjectMocks private ProductController productController;



 @Test


 public void testMethod() throws Exception {


 // Prepare sample test data.


 final Product product1 = Mockito.mock(Product.class);


 final Product product2 = Mockito.mock(Product.class);


 final ArrayList<Product> products = new ArrayList<Product>();


 products.add(product1);


 products.add(product2);


 final ModelMap mmap = Mockito.mock(ModelMap.class);



 // Configure the mocked objects.


 Mockito.when(product1.getId()).thenReturn(new Long(1));


 Mockito.when(product2.getId()).thenReturn(new Long(2));


 Mockito.when(mockproductService.findAllProducts()).thenReturn(products);


 final mmap = Mockito.mock(ModelMap.class);



 // Call the method under test.


 final String returned = productController.testController(mmap);



 // Check if everything went as planned.


 Mockito.verify(mmap).addAttribute("Products", products);


 assertNotNull(returned);


 assertEquals("show_products", returned);


 }


}



这就是单元测试的样子,另外,使用final可防止意外分配,换句话说,防止意外覆盖现有值。

第二,配置每个模拟对象的行为,如果一个Product将被要求提供ID,那么就指定模拟实例返回什么,


 final Product product1 = Mockito.mock(Product.class);


 final Product product2 = Mockito.mock(Product.class);


 final ArrayList<Product> products = new ArrayList<Product>();


 products.add(product1);


 products.add(product2);


 final mmap = Mockito.mock(ModelMap.class);



 // Configure the mocked objects.


 Mockito.when(mockproductService.findAllProducts()).thenReturn(products);


 final mmap = Mockito.mock(ModelMap.class);



第三,调用测试中的方法并存储它结果:


 final String returned = productController.testController(mmap);



在这种情况下,应该用这些确切的参数值调用ModelMap =s addAttribute()方法。


 Mockito.verify(mmap).addAttribute("Products", products);


 assertNotNull(returned);


 assertEquals("show_products", returned);



成功测试!

我忘了解释开头:


 @RunWith(MockitoJUnitRunner.class)


 public class ProductControllerTest {


 @Mock private ProductService mockproductService;


 @InjectMocks private ProductController productController;



如果运行提供的示例


@RunWith(MockitoJUnitRunner.class)



还有


 @Autowired private WebApplicationContext wac;



那测试就会失败,这个解决方案:

控制器:


@Controller


public class MyController


{


 @Autowired(required=false)


 MyService myService;


 .


 .


 .


}



测试:


@RunWith(SpringJUnit4ClassRunner.class)


@ContextConfiguration(locations="classpath:/META-INF/spring/applicationContext-test.xml")


@WebAppConfiguration


public class MyControllerTest


{


 // This is the backend service we are going to mock


 @Mock


 MyService myService;



 // This is the component we are testing and we inject our mocked


 // objects into it


 @InjectMocks


 @Resource


 private MyController myController;



 @Autowired


 private WebApplicationContext wac;



 private MockMvc mockMvc;



 @Before


 public void setup()


 {


 MockitoAnnotations.initMocks(this);


 this.mockMvc = webAppContextSetup(this.wac).build();



 List<Object> data = new ArrayList<Object>();



 // Mock one of the service mthods


 when(myService.getAll()).thenReturn(datasets); 


 }



 @Test


 public void testQuery() throws Exception


 {


 this.mockMvc.perform(get("/data").accept(MediaType.APPLICATION_JSON))


 .andExpect(status().isOk())


 .andExpect(content().contentType("application/json;charset=UTF-8"))


 .andExpect(jsonPath("$.value").value("Hello"));


 }



}



和应用程序上下文:


<?xml version="1.0" encoding="UTF-8" standalone="no"?>


<beans xmlns="http://www.springframework.org/schema/beans"


xmlns:context="http://www.springframework.org/schema/context"


xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"


xmlns:neo4j="http://www.springframework.org/schema/data/neo4j"


xmlns:mvc="http://www.springframework.org/schema/mvc"


xsi:schemaLocation="


http://www.springframework.org/schema/beans


http://www.springframework.org/schema/beans/spring-beans.xsd


http://www.springframework.org/schema/context


http://www.springframework.org/schema/context/spring-context.xsd


http://www.springframework.org/schema/data/neo4j


http://www.springframework.org/schema/data/neo4j/spring-neo4j.xsd


http://www.springframework.org/schema/mvc 


http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd 


">



 <mvc:annotation-driven/>


 <context:annotation-config/>


 <context:component-scan base-package="com.me.controller" /> 



</beans>



...