我似乎无法进行简单的REST客户端集成测试。我使用Spring 3.1 MVC与JavaConfig设置。我使用Maven,我可以毫无问题地构建,运行和部署我当前的webapp。
首先,这是一些代码和配置
我的控制器
@Controller
@RequestMapping("/rest")
public class StubRestController {
@Inject
private TestData testData;
@RequestMapping(value=Endpoints.GET_RESOURCES, method=RequestMethod.GET, produces="application/json")
@ResponseStatus(HttpStatus.OK)
public @ResponseBody JSONObject getResources(@RequestParam(value="q") String query, @RequestParam int indexFrom, @RequestParam int indexTo) throws JSONException {
return makeRequest(query, indexFrom, indexTo, testData.getResources());
}
@RequestMapping(value=Endpoints.GET_LOCATIONS, method=RequestMethod.GET, produces="application/json")
@ResponseStatus(HttpStatus.OK)
public @ResponseBody JSONObject getLocations(@RequestParam(value="q") String query, @RequestParam int indexFrom, @RequestParam int indexTo) throws JSONException {
return makeRequest(query, indexFrom, indexTo, testData.getLocations());
}
private JSONObject makeRequest(String query, int indexFrom, int indexTo, String[] data) throws JSONException {
int count = 0;
final JSONArray resources = new JSONArray();
for (final String resourceName: data) {
final String lowerColor = resourceName.toLowerCase();
final int has = lowerColor.indexOf(query.toLowerCase());
if (!query.isEmpty() && (query.equals("*") || has >= 0)) {
final JSONObject resource = new JSONObject();
resource.put("DisplayName", resourceName);
resource.put("Value", resourceName); // shouldn't this be a unique id? e.g., resourceid
resources.put(resource);
count++;
}
}
final JSONArray partial = new JSONArray();
if (resources.length() > 0) {
final int end = count - 1 > indexTo ? indexTo : count - 1;
for (int i = indexFrom; i <= end; i++) {
partial.put(resources.get(i));
}
}
final JSONObject result = new JSONObject();
result.put("TotalSize", count);
result.put("Options", partial);
return result;
}
@ExceptionHandler(JSONException.class)
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason="No data found matching criteria")
public void notFound() { }
}
我的测试
@ContextConfiguration(classes={ RestClientContext.class }, loader=AnnotationConfigContextLoader.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class StubRestClientITCase {
private static final String SCHEME = "http";
private static final String HOST = "localhost";
private static final int PORT = 8081;
// requests must match <url-pattern> in order to be handled by DispatcherServlet
private static final String ENDPOINT_PREFIX = "spp-emkt-mui-experimental/EMKT/services/rest";
private static final String QUERY_KEY = "q";
private static final String INDEX_FROM_KEY = "indexFrom";
private static final String INDEX_TO_KEY = "indexTo";
private static final String TOTAL_SIZE_KEY = "TotalSize";
private Logger log = LoggerFactory.getLogger(StubRestClientITCase.class);
@Inject
RestTemplate restTemplate;
@Test
public void testGetResources() {
// Case 1: Discover all resources using * (asterisk), first 25
final URI uri = buildUri("*", 0, 24, Endpoints.GET_RESOURCES);
final HttpEntity<JSONObject> response = obtainResponse(uri);
try {
Assert.assertTrue(response.hasBody());
Assert.assertEquals(25, Integer.parseInt(response.getBody().getString(TOTAL_SIZE_KEY)));
} catch (final JSONException je) {
fail("Could not obtain \"" + TOTAL_SIZE_KEY + "\" from JSON payload for getResources().\n" + je.getMessage());
}
}
private URI buildUri(String query, int indexFrom, int indexTo, String endPointUrl) {
final UriComponents uriComponents =
UriComponentsBuilder.newInstance()
.scheme(SCHEME).host(HOST).port(PORT).path(ENDPOINT_PREFIX + endPointUrl)
.queryParam(QUERY_KEY, query)
.queryParam(INDEX_FROM_KEY, indexFrom)
.queryParam(INDEX_TO_KEY, indexTo)
.build()
.encode();
final URI uri = uriComponents.toUri();
return uri;
}
private HttpEntity<JSONObject> obtainResponse(URI uri) {
final HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAccept(Arrays.asList(new MediaType[] {MediaType.APPLICATION_JSON}));
requestHeaders.setAcceptCharset(Arrays.asList(new Charset[] {Charset.forName("UTF-8")}));
final HttpEntity<?> requestEntity = new HttpEntity(requestHeaders);
final HttpEntity<JSONObject> response = restTemplate.exchange(uri, HttpMethod.GET, requestEntity, JSONObject.class);
return response;
}
}
我的web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<context-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</context-param>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.spp.mui.gwt.server.config.WebAppContextExperimental</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>gwt</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextClass</param-name>
<param-value>
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>gwt</servlet-name>
<url-pattern>/EMKT/service/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<session-config>
<session-timeout>0</session-timeout>
</session-config>
</web-app>
我可以在服务器启动时看到我的Controller方法...
17:48:58,651 INFO [RequestMappingHandlerMapping] Mapped "{[/rest/resources],methods=[GET],params=[],headers=[],consumes=[],produces=[application/json],custom=[]}"
17:48:58,651 INFO [RequestMappingHandlerMapping] Mapped "{[/rest/locations],methods=[GET],params=[],headers=[],consumes=[],produces=[application/json],custom=[]}"
当测试运行时,我得到......
17:49:00,617 DEBUG [AutowiredAnnotationBeanPostProcessor] Autowiring by type from bean name 'com.spp.mui.gwt.server.controller.stub.StubRestClientITCase' to bean named 'restTemplate'
17:49:00,648 DEBUG [RestTemplate] Created GET request for "http://localhost:8080/EMKT/services/rest/resources?q=*&indexFrom=0&indexTo=24"
17:49:00,680 DEBUG [RestTemplate] Setting request Accept header to [application/json]
17:49:00,742 WARN [RestTemplate] GET request for "http://localhost:8080/EMKT/services/rest/resources?q=*&indexFrom=0&indexTo=24" resulted in 404 (Not Found); invoking error handler
...最后
Tests in error:
testGetResources(com.spp.mui.gwt.server.controller.stub.StubRestClientITCase): 404 Not Found
可能是什么事?认为它与 servlet-mapping 中的 url-pattern 有关。我试图在设置上遵循Spring文档但是没有成功。注意:我没有灵活性,因为我有一个GWT-SL设置来映射RPC服务接口。
更新#1
如果我尝试使用
curl --verbose -H "Accept: application/json" "localhost:8081/spp-emkt-mui-experimental/EMKT/service/rest/resources?q=*&indexFrom=0&indexTo=24"
针对Tomcat 6部署(使用货物插件),我得到了不同的结果:
* About to connect() to localhost port 8081 (#0)
* Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 8081 (#0)
> GET /spp-emkt-mui-experimental/EMKT/service/rest/resources?q=*&indexFrom=0&indexTo=24 HTTP/1.1
> User-Agent: curl/7.21.1 (i686-pc-mingw32) libcurl/7.21.1 OpenSSL/0.9.8r zlib/1.2.3
> Host: localhost:8081
> Accept: */*
>
< HTTP/1.1 406 Not Acceptable
< Server: Apache-Coyote/1.1
< Content-Type: text/html;charset=utf-8
< Content-Length: 1070
< Date: Tue, 28 Feb 2012 05:00:26 GMT
这似乎表明我没有在我的请求中发送适当的标题信息。 Hmmmmmm。
更新#2
书面测试返回404.卷曲406.
我已经搜索了许多看似相关的帖子并尝试了很多东西,我想知道Spring MVC,GWT-RPC和GWT-SL是否能够组合在同一个容器中。考虑转向RestyGWT,服务器端是Spring MVC。评论
答案 0 :(得分:8)
这绝对是我的学习练习。我必须清除一些关于配置的障碍,以便GWT RPC和Spring MVC很好地协同工作。
我现在有一个工作测试。我的“失礼”试图返回@ResponseBody注释的JSONObject。 不要这样做!我制作了一个自定义DTO并注释了@JsonProperty,@ JsonSerialize和@JsonDeserialize等Jackson注释,以便按照我想要的方式获取I / O.
我将在下面展示我的工作测试和配置更新,以防有人感兴趣...
<强> TEST 强>
@ContextConfiguration(classes={ RestClientContext.class }, loader=AnnotationConfigContextLoader.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class SuggestionsClientITCase {
private static final String SCHEME = "http";
private static final String HOST = "localhost";
private static final int PORT = 8080;
private static final String QUERY_KEY = "q";
private static final String INDEX_FROM_KEY = "indexFrom";
private static final String INDEX_TO_KEY = "indexTo";;
private Logger log = LoggerFactory.getLogger(SuggestionsClientITCase.class);
@Inject
RestTemplate restTemplate;
@Test
public void testGetResources() {
// Case 1: Discover all resources using * (asterisk), first 25
// -- the total # of resources in TestData is 250
// -- the total # of options returned should be constrained by indexTo - indexFrom = 25
happyPathAssertions(Endpoints.GET_RESOURCES, "*", 0, 24, 250, 25);
}
@Test
public void testGetLocations() {
// Case 1: Discover all resources using * (asterisk), first 25
// -- the total # of locations in TestData is 4316
// -- the total # of options returned should be constrained by indexTo - indexFrom = 25
happyPathAssertions(Endpoints.GET_LOCATIONS, "*", 0, 24, 4316, 25);
}
private void happyPathAssertions(String endpointUrl, String query, int indexFrom, int indexTo, int expectedTotal, int expectedOptionsPerPage) {
final URI uri = buildUri(query, indexFrom, indexTo, endpointUrl);
final HttpEntity<SuggestionsPayload> response = obtainResponse(uri);
Assert.assertTrue(response.hasBody());
Assert.assertEquals(expectedTotal, response.getBody().getTotalSize());
Assert.assertEquals(expectedOptionsPerPage, response.getBody().getOptions().size());
}
private URI buildUri(String query, int indexFrom, int indexTo, String endPointUrl) {
final UriComponents uriComponents =
UriComponentsBuilder.newInstance()
.scheme(SCHEME).host(HOST).port(PORT).path(Endpoints.REST_PREFIX + endPointUrl)
.queryParam(QUERY_KEY, query)
.queryParam(INDEX_FROM_KEY, indexFrom)
.queryParam(INDEX_TO_KEY, indexTo)
.build()
.encode();
final URI uri = uriComponents.toUri();
return uri;
}
private HttpEntity<SuggestionsPayload> obtainResponse(URI uri) {
final HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setAccept(Arrays.asList(new MediaType[] {MediaType.APPLICATION_JSON}));
requestHeaders.setAcceptCharset(Arrays.asList(new Charset[] {Charset.forName("UTF-8")}));
final HttpEntity<?> requestEntity = new HttpEntity(requestHeaders);
final HttpEntity<SuggestionsPayload> response = restTemplate.exchange(uri, HttpMethod.GET, requestEntity, SuggestionsPayload.class);
return response;
}
}
<强> CONTROLLER 强>
@Controller
@RequestMapping("/" + Endpoints.REST_PREFIX)
public class StubSuggestionsController {
@Inject
private TestData testData;
@RequestMapping(value=Endpoints.GET_RESOURCES, method=RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
@ResponseStatus(HttpStatus.OK)
public @ResponseBody SuggestionsPayload getResources(@RequestParam(value="q") String query, @RequestParam int indexFrom, @RequestParam int indexTo) {
return makeRequest(query, indexFrom, indexTo, testData.getResources());
}
@RequestMapping(value=Endpoints.GET_LOCATIONS, method=RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE})
@ResponseStatus(HttpStatus.OK)
public @ResponseBody SuggestionsPayload getLocations(@RequestParam(value="q") String query, @RequestParam int indexFrom, @RequestParam int indexTo) {
return makeRequest(query, indexFrom, indexTo, testData.getLocations());
}
private SuggestionsPayload makeRequest(String query, int indexFrom, int indexTo, String[] data) {
int count = 0;
final List<SuggestionOption> possibilities = new ArrayList<SuggestionOption>();
for (final String resourceName: data) {
final String key = resourceName.toLowerCase();
final int has = key.indexOf(query.toLowerCase());
if (!query.isEmpty() && (query.equals("*") || has >= 0)) {
final SuggestionOption possibility = new SuggestionOption();
possibility.setDisplayName(resourceName);
possibility.setValue(resourceName); // shouldn't this be a unique id? e.g., resourceid
possibilities.add(possibility);
count++;
}
}
final List<SuggestionOption> options = new ArrayList<SuggestionOption>();
if (possibilities.size() > 0) {
final int end = count - 1 > indexTo ? indexTo : count - 1;
for (int i = indexFrom; i <= end; i++) {
options.add(possibilities.get(i));
}
// sort the suggestions by display name
Collections.sort(options, new Comparator<SuggestionOption>() {
@Override
public int compare(SuggestionOption o1, SuggestionOption o2) {
final int comparison = o1.getDisplayName().compareTo(o2.getDisplayName());
return comparison;
}
});
}
final SuggestionsPayload result = new SuggestionsPayload();
result.setTotalSize(count);
result.setOptions(options);
return result;
}
}
<强> PAYLOAD 强>
public class SuggestionsPayload {
@JsonProperty("TotalSize")
@JsonSerialize @JsonDeserialize
private int totalSize;
@JsonProperty("Options")
@JsonSerialize @JsonDeserialize
private List<SuggestionOption> options;
public int getTotalSize() {
return totalSize;
}
public void setTotalSize(int totalSize) {
this.totalSize = totalSize;
}
public List<SuggestionOption> getOptions() {
return options;
}
public void setOptions(List<SuggestionOption> options) {
this.options = options;
}
public static class SuggestionOption {
@JsonProperty("Value")
@JsonSerialize @JsonDeserialize
private String value;
@JsonProperty("DisplayName")
@JsonSerialize @JsonDeserialize
private String displayName;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}
}
<强> WEB.XML 强>
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<!-- Java-based annotation-driven Spring container definition -->
<context-param>
<param-name>contextClass</param-name>
<param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
</context-param>
<!-- Location of Java @Configuration classes that configure the components that makeup this application -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.spp.mui.gwt.server.config.WebAppContextExperimental</param-value>
</context-param>
<!-- Specifies the default mode of this application, to be activated if no other profile (or mode) is specified -->
<context-param>
<param-name>spring.profiles.default</param-name>
<param-value>standard</param-value>
</context-param>
<!-- Creates the Spring Container shared by all Servlets and Filters -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Exposes request on current thread of execution, required for beans
employing <aop:scoped-proxy /> or e.g., @Scope(value="request", proxyMode=ScopedProxyMode.TARGET_CLASS) -->
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
<!-- Reads request input using UTF-8 encoding -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Enables support for DELETE and PUT request methods with web browser clients -->
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- Secures the application -->
<!--
<filter>
<filter-name>securityFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>springSecurityFilterChain</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>securityFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
-->
<!-- Handles requests into the application -->
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- No explicit configuration file reference here: everything is configured in the root container for simplicity -->
<param-name>contextConfigLocation</param-name>
<param-value></param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>Application.html</welcome-file>
</welcome-file-list>
<session-config>
<session-timeout>0</session-timeout>
</session-config>
<强>配置强>
@Configuration
@Import(value={ AopConfig.class, GwtServiceConfig.class, ComponentConfig.class, MvcConfig.class })
public class WebAppContextExperimental {
// context used for web application
}
在我的MvcConfig中我必须确保添加这些(特别是GWT支持的注册表项,因此Spring的DispatcherServlet将允许请求代码生成资源)
// serve static resources
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// i.e., images, JS, and CSS
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
// i.e., GWT module code-generated resources
registry.addResourceHandler("/*").addResourceLocations("/");
registry.addResourceHandler("/EMKT/**").addResourceLocations("/EMKT/");
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJacksonHttpMessageConverter());
}