【问题标题】:Mockito Injecting Null values into a Spring bean when using @Mock?使用@Mock时Mockito将Null值注入Spring bean?
【发布时间】:2013-04-17 14:31:15
【问题描述】:

由于我是 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>

【问题讨论】:

标签: java spring unit-testing mockito


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案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);
    

    最后,您检查被测类是否按预期运行。在这种情况下,ModelMapaddAttribute() 方法应该使用这些精确的参数值调用。并且返回的字符串不应该是null,而应该是"show_products" - 注意assertEquals(expected, actual) 方法的参数顺序,因为在测试失败的情况下,JUnit 会打印出一条消息,上面写着“Expected THIS but got THAT.”。

            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;
    

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

    【讨论】:

      【解决方案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>
      

      【讨论】:

      • MockitoAnnotations.initMocks(this); 是调用@InjectMocks 的关键。
      • 是的。基本上任何标有@Mock@InjectMocks 的字段都不会被初始化,除非你调用它。总是最好在 @Before 注释方法中做到这一点
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-12
      • 1970-01-01
      相关资源
      最近更新 更多