问题
Have a codebase which uses SpringMVC 4.0.3.RELEASE for Restful Web Services.
Codebase contains a fully functional Restful Web Service which I can verify works using postman and curl.
However, when trying to write a unit test for the particular Restful Web Service using MockMvc, I become blocked with trying to obtain the JSON content from the unit test.
Am wondering if its a config issue or an issue where I am not creating a fake object correctly (since this doesn't rely on tomcat and is standalone).
pom.xml:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.0.3.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.0.3.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- A bunch of other Spring libs omitted from this post -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.10.19</version>
<exclusions>
<exclusion>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
</exclusion>
</exclusions>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path-assert</artifactId>
<version>0.9.1</version>
<scope>test</scope>
</dependency>
WebConfig:
package com.myapp.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@EnableWebMvc
@Configuration
@ComponentScan("com.myapp.rest")
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
}
ServletInitializer:
package com.myapp.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import com.myapp.config.WebConfig;
public class ServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
protected Class<?>[] getServletConfigClasses() {
return new Class[] { WebConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
}
UserController:
@RestController
@RequestMapping("/v2")
public class UserController {
private final UserDAO dao;
private HttpHeaders headers = null;
@Autowired
public UserController(UserDAO dao) {
this.dao = dao;
headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
}
@RequestMapping(value = "users/{appId}", method = RequestMethod.GET, produces="application/json")
@ResponseBody
public ResponseEntity<Object> getUserDetails(@PathVariable String appId) {
Object jsonPayload = dao.getUser(appId);
return new ResponseEntity<Object>(jsonPayload, headers, HttpStatus.OK);
}
}
UserDAO:
@Repository
public class UserDAO {
private JdbcTemplate jdbcTemplate;
@Autowired
public UserDAO(@Qualifier("dataSourceDB") DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
// Finders methods (such as the getUser(appId)) which use Spring JDBC
}
WEB-INF/web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<display-name>MyApp</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>mvc-dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc-dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/mvc-dispatcher-servlet.xml</param-value>
</context-param>
</web-app>
WEB-INF/mvc-dispatcher-servlet.xml:
<beans xmlns="http://www.springframework.org/schema/beans">
<import resource="classpath:database.xml" />
<context:component-scan base-package="com.myapp.rest" />
<mvc:annotation-driven />
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix">
<value>/WEB-INF/pages/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>
src/main/resources/database.xml:
<beans xmlns="http://www.springframework.org/schema/beans">
<bean id="dataSourceDB" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName"><value>${jdbc.driver}</value></property>
<property name="url"><value>${db.url}</value></property>
<property name="username"><value>${db.username}</value></property>
<property name="password"><value>${db.password}</value></property>
</bean>
</beans>
My actual unit test: package com.myapp.rest.controllers;
RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:**/mvc-dispatcher-servlet.xml")
@WebAppConfiguration
public class UserControllerTest {
private MockMvc mockMvc;
@Mock
private UserDAO dao;
@InjectMocks
private UserController controller;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
@Test
public void getUserDetails() throws Exception {
String appId = "23bdr4560l";
mockMvc.perform(get("/v2/users/{appId}",appId)
.accept(MediaType.APPLICATION_JSON))
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andDo(print());
}
}
When I invoke my build locally:
mvn clean install
Generated output to stdout:
Running com.myapp.rest.controllers.UserControllerTest
MockHttpServletRequest:
HTTP Method = GET
Request URI = /v2/users/23bdr4560l
Parameters = {}
Headers = {Accept=[application/json]}
Handler:
Type = com.myapp.rest.controllers.UserController
Method = public org.springframework.http.ResponseEntity<java.lang.Object> com.myapp.rest.controllers.UserController.getUserDetails(java.lang.String)
Async:
Was async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Type=[application/json]}
Content type = application/json
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.728 sec
Results :
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
Notice how the JSON body part is empty / null:
Body =
So, when trying to access the JSON payload using jsonPath:
@Test
public void getUserDetails() throws Exception {
String appId = "23bdr4560l";
mockMvc.perform(get("/v2/users/{appId}",appId)
.accept(MediaType.APPLICATION_JSON))
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$", hasSize(2)))
.andDo(print());
}
Received the following error:
Results :
Tests in error:
getUserDetails(com.myapp.rest.controllers.UserControllerTest): json can not be null or empty
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0
What am I possbily doing wrong?
Obviously, I need to mock out the response but have Googled this and others using MockMvc were able to obtain a JSON their test.
Is this a config issue (do I need to put some type of annotation for my unit test)?
Do, I need to instantiate the controller inside my test?
What weird is that the actual Rest Call works (returns a valid JSON) using postman and curl...
This codebase does not have an @Service class / layer, its just @RestController speaking to @Repository (see above).
Really thought that testing support for Spring MVC based Restful Web Services would be a lot easier.
Am running out of ideas...
Any help would me most appreciated...
回答1:
When you are trying to mock the bean like this, it tries to create the mock with the default constructor.
@Mock
private UserDAO dao
It works in case of actual rest call because you provide the dependent DataSource dataSource at runtime. Providing a valid dependent mock for the UserDAO class works.
回答2:
Since you are mocking the UserDAO you need to provide some mock expectations for the call:
Object jsonPayload = dao.getUser(appId);
Inside your Controller or it will return null by default. To do this use the Mockito.when() method inside your test prior to the MockMvc call:
when(dao.getUser(anyString())).thenReturn(json)
where json is some hard coded json Object you set up yourself.
回答3:
For starters, since you're using MockMvcBuilders.standaloneSetup(...) you are in fact not using the Spring TestContext Framework (TCF) to load your WebApplicationContext. So just completely delete the following declarations in your test class:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:**/mvc-dispatcher-servlet.xml")
@WebAppConfiguration
Also, you can completely ignore your XML configuration files since they are not used without a WebApplicationContext.
As an aside, web.xml and Servlet initializers are never used by the TCF, so you can ignore them as well.
Then, as mentioned by @Plog, you'll need to set up the expectations for your mocked UserDAO; otherwise, the invocation of dao.getUser(appId) always returns null which results in an empty response body.
Regards,
Sam (author of the Spring TestContext Framework)
来源:https://stackoverflow.com/questions/44793335/no-json-content-comes-up-null-from-mockmvc-test-using-spring-mvc-and-mockito