@Autowired依赖项的@Mock会导致随机的junit测试失败

时间:2016-06-04 02:42:46

标签: spring spring-mvc spring-test spring-mvc-test

我的spring-mvc app有效。好极了!证明:

enter image description here

这是我的好设置:

Buggy-servlet.xml

的重要部分
<import resource="classpath:bug-core.xml" />
<mvc:annotation-driven />
<context:component-scan base-package="buggy.bug" />

它导入的bug-core.xml文件的重要位:

<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="VersionInfoBean" class="buggy.bug.VersionInfo">
    <property name="helloWorld" value="GAHHHHH!!!" />
</bean>

VersionInfo类:

public class VersionInfo {

    private String helloWorld;

    public String getHelloWorld() {
        return helloWorld;
    }

    public void setHelloWorld(String helloWorld) {
        this.helloWorld = helloWorld;
    }

}

最后,VersionInfoController类:

@RestController
@RequestMapping("/versioninfo")
public class VersionInfoController {

    @Autowired
    private VersionInfo versionInfo;

    @ResponseStatus(value = HttpStatus.OK)
    @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
    public VersionInfo getVersionInfo () {
        return versionInfo;
    }

}

一切都很好!

现在问题是:

我想进行单元测试。我觉得我做得很好。我的VersionInfoControllerTest课程:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
@WebAppConfiguration
@TestExecutionListeners(listeners = {DependencyInjectionTestExecutionListener.class})
public class VersionInfoControllerTest {

    // TODO: apparently I cannot @Mock the VersionInfo.  Try uncommenting the below, run the test a few times and see.

    // The link is for testng, but it's nearly the same for junit, and SHOULD work!
    // https://lkrnac.net/blog/2014/01/mock-autowired-fields/

//  @Mock
//  private VersionInfo versionInfo;

    @InjectMocks
    private VersionInfoController versionInfoController;

    private MockMvc mockMvc;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        mockMvc = MockMvcBuilders.standaloneSetup(versionInfoController).build();
    }

    @Test
    public void getVersionInfo() throws Exception {
        mockMvc.perform(get("/versioninfo")
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isOk());
    }
}

WebAppContext类:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"buggy.bug"})
public class WebAppContext extends WebMvcConfigurerAdapter {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

}

TestContext类:

@Configuration
public class TestContext {

    @Bean
    public VersionInfo versionInfo() {
        return Mockito.mock(VersionInfo.class);
    }
}

我运行junit(通过mvn clean install或在Eclipse junit启动配置中运行)。这一切都很好。

如果我取消注释VersionInfoControllerTest中指示的两行,则测试可能会失败或通过(更常见的是失败)。当它失败时,它会以两种方式之一失败:

方式一:

java.lang.AssertionError: Status expected:<200> but was:<500>
    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.StatusResultMatchers$10.match(StatusResultMatchers.java:655)
    at org.springframework.test.web.servlet.MockMvc$1.andExpect(MockMvc.java:171)
    at buggy.bug.VersionInfoControllerTest.getVersionInfo(VersionInfoControllerTest.java:52)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)

方式二更糟糕:

INFO: FrameworkServlet '': initialization completed in 1 ms
Jun 03, 2016 8:44:06 PM org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver handleHttpMessageNotWritable
WARNING: Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: Infinite recursion (StackOverflowError) (through reference chain: org.mockito.internal.stubbing.InvocationContainerImpl["invocationForStubbing"]->org.mockito.internal.invocation.InvocationMatcher["invocation"]->org.mockito.internal.invocation.InvocationImpl["mock"]->buggy.bug.VersionInfo$$EnhancerByMockitoWithCGLIB$$41545457["callbacks"]->org.mockito.internal.creation.MethodInterceptorFilter["handler"]->org.mockito.internal.handler.InvocationNotifierHandler["invocationContainer"]->org.mockito.internal.stubbing.InvocationContainerImpl["invocationForStubbing"]->org.mockito.internal.invocation.InvocationMatcher["invocation"]->org.mockito.internal.invocation.InvocationImpl["mock"]->buggy.bug.VersionInfo$$EnhancerByMockitoWithCGLIB$$41545457["callbacks"]->org.mockito.internal.creation.MethodInterceptorFilter["handler"]->org.mockito.internal.handler.InvocationNotifierHandler["invocationContainer"]->org.mockito.internal.stubbing.InvocationContainerImpl["invocationForStubbing"]->...

因为它是StackOverflowError,所以重复直到内存不足。

我提出了github project that demonstrates the problem

有什么想法吗?我做错了什么?据我所知,我确实在SO和博客,论坛,春季文档上做了其他接受的答案,所有人都说要模拟@Autowired字段。

1 个答案:

答案 0 :(得分:4)

这里有两件事情。

  1. 由于您正在使用MockMvcBuilders.standaloneSetup,因此需要加载ApplicationContext
  2. 杰克逊无法将您的VersionInfo对象转换为JSON,如果它是由Mockito创建的模拟(至少没有使用默认的映射规则来尝试映射包括Mockito引入的所有属性)。
  3. 以下是解决方案:

    public class VersionInfo {
    
        @JsonView(VersionInfo.class)
        private String helloWorld;
    
        public String getHelloWorld() {
            return helloWorld;
        }
    
        public void setHelloWorld(String helloWorld) {
            this.helloWorld = helloWorld;
        }
    
    }
    
    @RestController
    @RequestMapping("/versioninfo")
    public class VersionInfoController {
    
        @Autowired
        private VersionInfo versionInfo;
    
        @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
        @JsonView(VersionInfo.class)
        public VersionInfo getVersionInfo() {
            return versionInfo;
        }
    
    }
    
    public class VersionInfoControllerTest {
    
        @Mock
        private VersionInfo versionInfo;
    
        @InjectMocks
        private VersionInfoController versionInfoController;
    
        private MockMvc mockMvc;
    
        @Before
        public void setUp() {
            MockitoAnnotations.initMocks(this);
            mockMvc = MockMvcBuilders.standaloneSetup(versionInfoController).build();
        }
    
        @Test
        public void getVersionInfo() throws Exception {
            mockMvc.perform(get("/versioninfo").accept(MediaType.APPLICATION_JSON))
                .andExpect(status().isOk());
        }
    
    }
    

    要点:

    1. 使用标准JUnit 4测试支持并删除与 Spring TestContext Framework 相关的所有内容,因为您没有使用ApplicationContext
    2. 使用@JsonView中的VersionInfo将JSON序列化仅限制为VersionInfo中的属性。
    3. 在调用控制器中的@JsonView方法时使用getVersionInfo()指示Spring在调用Jackson JSON映射器时使用视图
    4. 请记住,在这种情况下使用@JsonView只是必要的,因为您使用Mockito模拟控制器方法的返回值。

      此致

      Sam( Spring TestContext Framework的作者