Mockito在使用@Mock时将Null值注入Spring bean?

时间:2013-04-17 14:31:15

标签: java spring unit-testing mockito

由于我是Spring Test MVC的新手,我不明白这个问题。我从http://markchensblog.blogspot.in/search/label/Spring

中获取了以下代码

变量mockproductService未从Application Context注入,并且在使用null注释并获得断言错误时包含@Mock值。

我目前遇到的断言错误如下:

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>

3 个答案:

答案 0 :(得分:7)

对于我来说,目前还不清楚Spring和Mockito的组合如何从引用的博客源中获取它应该如预期的那样工作。至少我可以解释你的观察:

  • 您的测试(this.mockMvc.perform())正在处理由Spring创建的Web应用程序上下文。在该上下文中,ProductController由Spring(context:component-scan)实例化。然后productService使用您在mvc-dispatcher-servlet.xml中创建的Mockito模拟作为someDependencyMock进行自动装配。
  • 如果您通过mockproductService注入@Autowired Spring 会从其上下文中注入someDependencyMock实例。因此,您的Mockito.when()调用在此实例上正常运行,该实例已正确连接到前面提到的ProductController
  • 但是,如果您通过mockproductService注入@Mock Mockito 会注入 ProductService的实例,而不是Spring语境,因为它根本不了解Spring。因此,Mockito.when()调用不会对Spring自动装配的模拟进行操作,因此someDependencyMock保持未初始化状态。

所以我不清楚博客的原始代码是如何运作的:

  • 使用productController注释的@InjectMocks属性将由Mockito初始化,甚至正确连接到测试类中的mockproductService。但Spring对该对象一无所知,也不会在this.mockMvc.perform()调用中使用它。因此,我假设即使您在测试类中同时删除mockproductService属性和@Autowired调用,只有productController注入MockitoAnnotations.initMocks()您的测试才能正常工作。

答案 1 :(得分:2)

我没有看过你提到的教程,因为你提供的代码足以说明原作者的专业化或缺乏专业知识。

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

一个类很少依赖于任何东西,所以要创建一个真正好的单元测试,你需要模拟它的所有依赖

@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,那么您可以指定在这种情况下被模拟的实例将返回的内容。顺便说一句,我真的没有看到设置这些产品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的{​​{1}}方法。返回的字符串不应该是addAttribute(),而应该是null - 请注意"show_products"方法的参数顺序,因为如果测试失败,JUnit会打印出一条消息“预期的这个但得到了。“。

assertEquals(expected, actual)

祝你好运测试!

P.S。 我忘记解释开头了:

        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; 像Spring @InjectMocks一样工作,测试必须与@Autowired类一起运行 - 它将找到所有MockitoJUnitRunner成员,创建它们并注入正确的成员标记为@Mock

答案 2 :(得分:2)

我认为@Cebence提供的答案存在问题,因为它没有考虑OP对spring-webmvc-test @WebApplication的使用。如果您是运行

提供的示例
@RunWith(MockitoJUnitRunner.class)

你还有你的

 @Autowired private WebApplicationContext wac;

然后测试将失败。我遇到了与@Human Being相同的问题,我发现一个简单的解决方案是在控制器内设置不需要的服务。不理想,但这是解决方案:

控制器:

@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>