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

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

我的 spring-mvc 应用程序可以运行。耶!证明:

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 和博客、论坛、spring 文档上其他接受的答案,都说要模拟 @Autowired 字段。

最佳答案

这里发生了两件事。

  1. 由于您使用的是 MockMvcBuilders.standaloneSetup,因此您不需要需要加载 ApplicationContext
  2. 如果您的 VersionInfo 对象是 Mockito 创建的模拟,则 Jackson 无法将其转换为 JSON(至少不能使用尝试映射所有属性(包括 Mockito 引入的属性)的默认映射规则)。

解决方案如下:

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. VersionInfo 中使用 @JsonView 将 JSON 序列化仅限于 VersionInfo 中的属性。
  3. 在 Controller 中的 getVersionInfo() 方法上使用 @JsonView 来指示 Spring 在调用 Jackson JSON 映射器时使用 View

请记住,在这种情况下使用 @JsonView 仅是因为您使用 Mockito 模拟 Controller 方法的返回值。

问候,

Sam(Spring TestContext 框架的作者)

关于spring - @Autowired 依赖项的 @Mock 导致随机 junit 测试失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37625813/

相关文章:

java - Spring Autowiring 的dao为空

java - 如何检索音量参数并使其显示在 result.jsp 中

java - @WithUserDetails 和 spring boot 1.4 TestEntityManager 问题

java - Spring+quartz 给出 java.lang.NoClassDefFoundError : weblogic/logging/LogEntryFormatter

spring - 处理将用户名作为 url 一部分的 url 模式

java - Spring Boot - @PreAuthorize 在测试中不起作用

spring-mvc - 无法在使用 SpringJUnit4ClassRunner 的测试中加载 Spring 应用程序上下文

java - 使用mockito注入(inject)带有真实参数的bean

java - 通过java的Azure存储CRUD

java - 升级到 Java 7 后 Spring (3.2.0) ConfigurationClassEnhancer 中出现 Stackoverflow 错误