java 使用@Mock 时 Mockito 将空值注入 Spring bean?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16062842/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 21:46:39  来源:igfitidea点击:

Mockito Injecting Null values into a Spring bean when using @Mock?

javaspringunit-testingmockito

提问by Human Being

As I am new to Spring Test MVC I don't understand this problem. I took the code below from http://markchensblog.blogspot.in/search/label/Spring

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

Variable mockproductServiceis not injected from Application Context and it contains nullvalues while using @Mockannotation and getting assetion error.

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

The Assertion error I currently encounter is as follows:

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

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.match(ModelResultMatchers.java:68)
    at org.springframework.test.web.servlet.MockMvc.andExpect(MockMvc.java:141)
    at com.pointel.spring.test.ProductControllerTest.testMethod(ProductControllerTest.java:84)

Note: If I use @Autowiredinstead of @Mockit is working fine.

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

Test Controller class

测试控制器类

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 class

控制器类

@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

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>

回答by Heri

For me it's unclear how the combination of Spring and Mockito as you took it from the referenced blog source should work at all as expected. At least I may explain your observation:

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

  • Your test (this.mockMvc.perform()) is working on the web application context created by Spring. In that context ProductControllerwas instantiated by Spring (context:component-scan). The productServicewas then autowired with the Mockito mock you created in mvc-dispatcher-servlet.xmlas someDependencyMock.
  • If you inject the mockproductServicevia @Autowired, Springinjects the someDependencyMockinstance from its context. So your Mockito.when()call works correctly on this instance, which was already correctly wired to the ProductControlleras mentioned before.
  • But if you inject the mockproductServicevia @Mock, Mockitoinjects a newinstance of ProductService, not the one of the Spring context, since it knows nothing about Spring at all. So your Mockito.when()call does not operate on the mock which was autowired by Spring and thus someDependencyMockstays uninitialized.
  • 您的测试 ( this.mockMvc.perform()) 正在处理 Spring 创建的 Web 应用程序上下文。在那个上下文中ProductController被 Spring ( context:component-scan)实例化。在productService随后与您创建的模拟的Mockito自动连接mvc-dispatcher-servlet.xmlsomeDependencyMock
  • 如果注入mockproductServicevia @AutowiredSpringsomeDependencyMock将从其上下文注入实例。因此,您的Mockito.when()调用在此实例上正常工作,该实例已正确连接到ProductController前面提到的 。
  • 但是如果你注入了mockproductServicevia @MockMockito 会注入一个 的实例ProductService,而不是 Spring 上下文中的一个,因为它对 Spring 一无所知。因此,您的Mockito.when()调用不会对 Spring 自动装配的模拟进行操作,因此someDependencyMock保持未初始化状态。

So what's left unclear for me about how the original code from the blog worked at all is:

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

  • The productControllerproperty annotated with @InjectMockswill be initialized by Mockito and even correctly wired to the mockproductServicein the test class. But Spring does not know anything about that object and won't use it in this.mockMvc.perform()calls. So I assume if you inject mockproductServiceonly with @Autowiredyour test works as intended even if you delete both the productControllerproperty and the MockitoAnnotations.initMocks()call in your test class.
  • productController带有注释的属性@InjectMocks将由 Mockito 初始化,甚至正确连接到mockproductService测试类中的 。但是 Spring 对该对象一无所知,也不会在this.mockMvc.perform()调用中使用它。所以我假设如果你mockproductService只注入@Autowired你的测试,即使你删除了测试类中的productController属性和MockitoAnnotations.initMocks()调用也是如此。

回答by Cebence

I haven't looked at the tutorial you mentioned because the code you provided says enough on expertize, or lack of it, of the original author.

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

General rule of testing is that you don't mix different types of tests. First level of testing are unit testswhich mean that you are testing a single unit of work(usually a single class). Once the unit tests pass you write integration teststhat combine certain components (classes) and test how they work together.

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

A class rarely depends on nothing so to create a real good unit test you need to mock all its dependencies.

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

@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);
    }
}

That is how a unit test should look like. First you prepare the data (objects) - notice they are all mocked. Also, using finalprevents accidental assignments, i.e. to overwrite existing value by accident.

这就是单元测试的样子。首先准备数据(对象)——注意它们都是模拟的。此外,使用final可以防止意外分配,即意外覆盖现有值。

Second, you configure every mocked object's behavior. If a Productwill be asked for ID then you specify what the mocked instance will return in that case. BTW I really don't see the purpose of setting those product IDs so the first part of the test could look like this:

其次,您配置每个模拟对象的行为。如果 aProduct将被要求提供 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);

Third, call the method under test and store its result:

第三,调用被测方法并存储其结果:

        final String returned = productController.testController(mmap);

And finally you check if the class under test behaved as expected. In this case ModelMap's addAttribute()method should have been called with those exact parameter values. And returned string should not be null, and should be "show_products"- note the parameter order of assertEquals(expected, actual)method because, in case of failed test, JUnit will print out a message saying "Expected THIS but got THAT.".

最后,您检查被测类的行为是否符合预期。在这种情况下,应该使用那些确切的参数值调用ModelMapaddAttribute()方法。并且返回的字符串不应该是null,应该是"show_products"- 注意assertEquals(expected, actual)方法的参数顺序,因为如果测试失败,JUnit 将打印出一条消息,说“预期这个但得到了那个。”。

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

Good luck testing!

祝测试顺利!

P.S. I forgot to explain the beginning:

PS开头忘记解释了:

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

In order for the @InjectMocksto work like Spring's @Autowiredthe test must be ran with MockitoJUnitRunnerclass - it will locate all @Mockmembers, create them and inject the right ones into the member marked with @InjectMocks.

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

回答by Sparm

I think there's a problem with the answer that @Cebence provided in that it doesnt take into account the OP's usage of spring-webmvc-test @WebApplication. If you were you run the example provided with

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

@RunWith(MockitoJUnitRunner.class)

and you still have your

你还有你的

 @Autowired private WebApplicationContext wac;

Then the test will fail. I was experiencing the same problem as @Human Being and I found an easy solution was to set the service within the controller as not required. Not ideal but here's the solution:

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

The controller:

控制器:

@Controller
public class MyController
{
    @Autowired(required=false)
    MyService myService;
    .
    .
    .
}

The Test:

考试:

@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"));
    }

}

and application context:

和应用上下文:

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