3

由于我是 Spring Test MVC 的新手,所以我不明白这个问题。我从 http://markchensblog.blogspot.in/search/label/Spring获取了下面的代码

变量mockproductService不是从应用程序上下文注入的,它null在使用@Mock注释和获取资产错误时包含值。

我目前遇到的Assertion错误如下:

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

3 回答 3

8

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

  • 您的测试 ( this.mockMvc.perform()) 正在处理 Spring 创建的 Web 应用程序上下文。在那个上下文中ProductController是由 Spring ( context:component-scan) 实例化的。然后与您在as中productService创建的 Mockito 模拟自动连接。mvc-dispatcher-servlet.xmlsomeDependencyMock
  • 如果注入mockproductServicevia @AutowiredSpringsomeDependencyMock会从其上下文中注入实例。因此,您的呼叫在此实例上正常工作,如前所述Mockito.when(),该实例已正确连接到。ProductController
  • 但是如果你注入mockproductServicevia @MockMockito 会注入一个实例ProductService,而不是 Spring 上下文之一,因为它对 Spring 一无所知。因此,您的Mockito.when()调用不会在 Spring 自动装配的模拟上运行,因此someDependencyMock保持未初始化状态。

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

  • 带有注释的productController属性@InjectMocks将由 Mockito 初始化,甚至正确连接到mockproductService测试类中的 。但是 Spring 对该对象一无所知,也不会在this.mockMvc.perform()调用中使用它。因此,我假设即使您删除了测试类中的属性和调用,如果您mockproductService只按预期注入测试工作。@AutowiredproductControllerMockitoAnnotations.initMocks()
于 2013-04-17T21:55:16.620 回答
3

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

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

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

@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将要求 a 提供 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的方法。addAttribute()并且返回的字符串不应该是null,并且应该是"show_products"- 注意方法的参数顺序,assertEquals(expected, actual)因为在测试失败的情况下,JUnit 会打印出一条消息说“期望这个,但得到那个。”。

        Mockito.verify(mmap).addAttribute("Products", products);
        assertNotNull(returned);
        assertEquals("show_products", returned);

祝测试顺利!

PS我忘了解释开头:

    @RunWith(MockitoJUnitRunner.class)
    public class ProductControllerTest {
        @Mock private ProductService mockproductService;
        @InjectMocks private ProductController productController;

为了@InjectMocks像 Spring 一样工作@Autowired,测试必须与MockitoJUnitRunner类一起运行 - 它会找到所有@Mock成员,创建它们并将正确的注入到标记为的成员中@InjectMocks

于 2013-04-17T22:54:21.713 回答
3

我认为@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>
于 2014-01-24T15:17:45.347 回答