我的spring-mvc app有效。好极了!证明:
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字段。
答案 0 :(得分:4)
这里有两件事情。
MockMvcBuilders.standaloneSetup
,因此不需要加载ApplicationContext
。VersionInfo
对象转换为JSON,如果它是由Mockito创建的模拟(至少没有使用默认的映射规则来尝试映射包括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());
}
}
要点:
ApplicationContext
。@JsonView
中的VersionInfo
将JSON序列化仅限制为VersionInfo
中的属性。@JsonView
方法时使用getVersionInfo()
指示Spring在调用Jackson JSON映射器时使用视图。请记住,在这种情况下使用@JsonView
只是必要的,因为您使用Mockito模拟控制器方法的返回值。
此致
Sam( Spring TestContext Framework的作者)