Browse Source

Introduce support for HtmlUnit in Spring MVC Test

This commit introduces integration between MockMvc and HtmlUnit, thus
simplifying end-to-end testing when using HTML-based views and enabling
developers to do the following.

 - Easily test HTML pages using tools such as HtmlUnit, WebDriver, & Geb
   without the need to deploy to a Servlet container

 - Test JavaScript within pages

 - Optionally test using mock services to speed up testing

 - Share logic between in-container, end-to-end tests and
   out-of-container integration tests

Issue: SPR-13158
pull/849/head
Rob Winch 11 years ago committed by Sam Brannen
parent
commit
b73e39423c
  1. 4
      build.gradle
  2. 93
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java
  3. 39
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/ForwardRequestPostProcessor.java
  4. 92
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcher.java
  5. 523
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java
  6. 97
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java
  7. 150
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java
  8. 156
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java
  9. 93
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java
  10. 50
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcher.java
  11. 34
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/WebRequestMatcher.java
  12. 7
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/package-info.java
  13. 114
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java
  14. 96
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java
  15. 7
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/package-info.java
  16. 137
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java
  17. 31
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/ForwardController.java
  18. 34
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HelloController.java
  19. 79
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcherTests.java
  20. 782
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java
  21. 155
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java
  22. 117
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java
  23. 92
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java
  24. 135
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java
  25. 43
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcherTests.java
  26. 136
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java
  27. 55
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriverTests.java
  28. 772
      src/asciidoc/testing.adoc

4
build.gradle

@ -44,6 +44,7 @@ configure(allprojects) { project -> @@ -44,6 +44,7 @@ configure(allprojects) { project ->
ext.httpclientVersion = "4.5"
ext.httpasyncVersion = "4.1"
ext.jackson2Version = "2.6.0"
ext.htmlunitVersion = "2.17"
ext.jasperreportsVersion = "6.1.0"
ext.javamailVersion = "1.5.4"
ext.jettyVersion = "9.3.1.v20150714"
@ -56,6 +57,7 @@ configure(allprojects) { project -> @@ -56,6 +57,7 @@ configure(allprojects) { project ->
ext.poiVersion = "3.12"
ext.protobufVersion = "2.6.1"
ext.reactorVersion = "2.0.4.RELEASE"
ext.seleniumVersion = "2.46.0"
ext.slf4jVersion = "1.7.12"
ext.snakeyamlVersion = "1.15"
ext.snifferVersion = "1.14"
@ -1009,6 +1011,8 @@ project("spring-test") { @@ -1009,6 +1011,8 @@ project("spring-test") {
optional("com.jayway.jsonpath:json-path:2.0.0")
optional("org.skyscreamer:jsonassert:1.2.3")
optional("xmlunit:xmlunit:${xmlunitVersion}")
optional("net.sourceforge.htmlunit:htmlunit:$htmlunitVersion")
optional("org.seleniumhq.selenium:selenium-htmlunit-driver:$seleniumVersion")
testCompile(project(":spring-context-support"))
testCompile(project(":spring-oxm"))
testCompile("javax.mail:javax.mail-api:${javamailVersion}")

93
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java

@ -0,0 +1,93 @@ @@ -0,0 +1,93 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import com.gargoylesoftware.htmlunit.WebConnection;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import org.springframework.util.Assert;
/**
* <p>
* Implementation of WebConnection that allows delegating to various WebConnection implementations. For example, if
* you host your JavaScript on the domain code.jquery.com, you might want to use the following:</p>
* <pre>
* WebClient webClient = new WebClient();
*
* MockMvc mockMvc = ...
* MockMvcWebConnection mockConnection = new MockMvcWebConnection(mockMvc);
* mockConnection.setWebClient(webClient);
*
* WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*");
* WebConnection httpConnection = new HttpWebConnection(webClient);
* WebConnection webConnection = new DelegatingWebConnection(mockConnection, new DelegateWebConnection(cdnMatcher, httpConnection));
*
* webClient.setWebConnection(webConnection);
*
* WebClient webClient = new WebClient();
* webClient.setWebConnection(webConnection);
* </pre>
* @author Rob Winch
* @since 4.2
*/
public final class DelegatingWebConnection implements WebConnection {
private final List<DelegateWebConnection> connections;
private final WebConnection defaultConnection;
public DelegatingWebConnection(WebConnection defaultConnection, List<DelegateWebConnection> connections) {
Assert.notNull(defaultConnection, "defaultConnection cannot be null");
Assert.notEmpty(connections, "connections cannot be empty");
this.connections = connections;
this.defaultConnection = defaultConnection;
}
public DelegatingWebConnection(WebConnection defaultConnection,DelegateWebConnection... connections) {
this(defaultConnection, Arrays.asList(connections));
}
@Override
public WebResponse getResponse(WebRequest request) throws IOException {
for(DelegateWebConnection connection : connections) {
if(connection.getMatcher().matches(request)) {
return connection.getDelegate().getResponse(request);
}
}
return defaultConnection.getResponse(request);
}
public final static class DelegateWebConnection {
private final WebRequestMatcher matcher;
private final WebConnection delegate;
public DelegateWebConnection(WebRequestMatcher matcher, WebConnection delegate) {
this.matcher = matcher;
this.delegate = delegate;
}
private WebRequestMatcher getMatcher() {
return matcher;
}
private WebConnection getDelegate() {
return delegate;
}
}
}

39
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/ForwardRequestPostProcessor.java

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.util.Assert;
/**
* @author Rob Winch
* @since 4.2
*/
final class ForwardRequestPostProcessor implements RequestPostProcessor {
private final String forwardUrl;
public ForwardRequestPostProcessor(String url) {
Assert.hasText(url, "Forward url must have text");
forwardUrl = url;
}
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.setServletPath(forwardUrl);
return request;
}
}

92
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcher.java

@ -0,0 +1,92 @@ @@ -0,0 +1,92 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.net.URL;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.gargoylesoftware.htmlunit.WebRequest;
/**
* <p>
* An implementation of WebRequestMatcher that allows matching on the host and optionally
* the port of WebRequest#getUrl(). For example, the following would match any request to
* the host "code.jquery.com" without regard for the port:
* </p>
*
* <pre>
* WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com");
* </pre>
*
* Multiple hosts can also be passed in. For example, the following would match an request
* to the host "code.jquery.com" or the host "cdn.com" without regard for the port:
*
* <pre>
* WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com", "cdn.com");
* </pre>
*
* <p>
* Alternatively, one can also specify the port. For example, the following would match
* any request to the host "code.jquery.com" with the port of 80.
* </p>
*
* <pre>
* WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com:80");
* </pre>
*
* <p>
* The above cdnMatcher would match: "http://code.jquery.com/jquery.js" (default port of
* 80) and "http://code.jquery.com:80/jquery.js". However, it would not match
* "https://code.jquery.com/jquery.js" (default port of 443).
* </p>
*
* @author Rob Winch
* @since 4.2
* @see UrlRegexRequestMatcher
* @see org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection
*/
public final class HostRequestMatcher implements WebRequestMatcher {
private final Set<String> hosts = new HashSet<String>();
/**
* Creates a new instance
*
* @param hosts the hosts to match on (i.e. "localhost", "example.com:443")
*/
public HostRequestMatcher(String... hosts) {
this.hosts.addAll(Arrays.asList(hosts));
}
@Override
public boolean matches(WebRequest request) {
URL url = request.getUrl();
String host = url.getHost();
if(hosts.contains(host)) {
return true;
}
int port = url.getPort();
if(port == -1) {
port = url.getDefaultPort();
}
String hostAndPort = host + ":" + port;
return hosts.contains(hostAndPort);
}
}

523
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java

@ -0,0 +1,523 @@ @@ -0,0 +1,523 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import com.gargoylesoftware.htmlunit.CookieManager;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import org.springframework.beans.Mergeable;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.SmartRequestBuilder;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.util.Assert;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
/**
* <p>
* Internal class used to allow a {@link WebRequest} into a {@link MockHttpServletRequest} using Spring MVC Test's
* {@link RequestBuilder}.
* </p>
* <p>
* By default the first path segment of the URL is used as the contextPath. To override this default see
* {@link #setContextPath(String)}.
* </p>
*
* @author Rob Winch
* @since 4.2
* @see MockMvcWebConnection
*/
final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable {
private final Map<String, MockHttpSession> sessions;
private final WebClient webClient;
private final WebRequest webRequest;
private String contextPath;
private RequestBuilder parentBuilder;
private SmartRequestBuilder parentPostProcessor;
private RequestPostProcessor forwardPostProcessor;
/**
*
* @param sessions A {@link Map} of the {@link HttpSession#getId()} to currently managed {@link HttpSession}
* objects. Cannot be null.
* @param webClient the WebClient for retrieving cookies
* @param webRequest The {@link WebRequest} to transform into a {@link MockHttpServletRequest}. Cannot be null.
*/
public HtmlUnitRequestBuilder(Map<String, MockHttpSession> sessions, WebClient webClient,
WebRequest webRequest) {
Assert.notNull(sessions, "sessions cannot be null");
Assert.notNull(webClient, "webClient cannot be null");
Assert.notNull(webRequest, "webRequest cannot be null");
this.sessions = sessions;
this.webClient = webClient;
this.webRequest = webRequest;
}
public MockHttpServletRequest buildRequest(ServletContext servletContext) {
String charset = getCharset();
String httpMethod = webRequest.getHttpMethod().name();
UriComponents uriComponents = uriComponents();
MockHttpServletRequest result = new HtmlUnitMockHttpServletRequest(servletContext, httpMethod,
uriComponents.getPath());
parent(result, parentBuilder);
result.setServerName(uriComponents.getHost()); // needs to be first for additional headers
authType(result);
result.setCharacterEncoding(charset);
content(result, charset);
contextPath(result, uriComponents);
contentType(result);
cookies(result);
headers(result);
locales(result);
servletPath(uriComponents, result);
params(result, uriComponents);
ports(uriComponents, result);
result.setProtocol("HTTP/1.1");
result.setQueryString(uriComponents.getQuery());
result.setScheme(uriComponents.getScheme());
pathInfo(uriComponents,result);
return postProcess(result);
}
private MockHttpServletRequest postProcess(MockHttpServletRequest request) {
if(parentPostProcessor != null) {
request = parentPostProcessor.postProcessRequest(request);
}
if(forwardPostProcessor != null) {
request = forwardPostProcessor.postProcessRequest(request);
}
return request;
}
private void parent(MockHttpServletRequest result, RequestBuilder parent) {
if(parent == null) {
return;
}
MockHttpServletRequest parentRequest = parent.buildRequest(result.getServletContext());
// session
HttpSession parentSession = parentRequest.getSession(false);
if(parentSession != null) {
Enumeration<String> attrNames = parentSession.getAttributeNames();
while(attrNames.hasMoreElements()) {
String attrName = attrNames.nextElement();
Object attrValue = parentSession.getAttribute(attrName);
result.getSession().setAttribute(attrName, attrValue);
}
}
// header
Enumeration<String> headerNames = parentRequest.getHeaderNames();
while(headerNames.hasMoreElements()) {
String attrName = headerNames.nextElement();
Enumeration<String> attrValues = parentRequest.getHeaders(attrName);
while(attrValues.hasMoreElements()) {
String attrValue = attrValues.nextElement();
result.addHeader(attrName, attrValue);
}
}
// parameter
Map<String, String[]> parentParams = parentRequest.getParameterMap();
for(Map.Entry<String,String[]> parentParam : parentParams.entrySet()) {
String paramName = parentParam.getKey();
String[] paramValues = parentParam.getValue();
result.addParameter(paramName, paramValues);
}
// cookie
Cookie[] parentCookies = parentRequest.getCookies();
if(parentCookies != null) {
result.setCookies(parentCookies);
}
// request attribute
Enumeration<String> parentAttrNames = parentRequest.getAttributeNames();
while(parentAttrNames.hasMoreElements()) {
String parentAttrName = parentAttrNames.nextElement();
result.setAttribute(parentAttrName, parentRequest.getAttribute(parentAttrName));
}
}
/**
* Sets the contextPath to be used. The value may be null in which case the first path segment of the URL is turned
* into the contextPath. Otherwise it must conform to {@link HttpServletRequest#getContextPath()} which states it
* can be empty string or it must start with a "/" and not end in a "/".
*
* @param contextPath A valid contextPath
* @throws IllegalArgumentException if contextPath is not a valid {@link HttpServletRequest#getContextPath()}.
*/
public void setContextPath(String contextPath) {
if (contextPath == null || "".equals(contextPath)) {
this.contextPath = contextPath;
return;
}
if (contextPath.endsWith("/")) {
throw new IllegalArgumentException("contextPath cannot end with /. Got '" + contextPath + "'");
}
if (!contextPath.startsWith("/")) {
throw new IllegalArgumentException("contextPath must start with /. Got '" + contextPath + "'");
}
this.contextPath = contextPath;
}
public void setForwardPostProcessor(RequestPostProcessor postProcessor) {
this.forwardPostProcessor = postProcessor;
}
private void authType(MockHttpServletRequest request) {
String authorization = header("Authorization");
if (authorization != null) {
String[] authzParts = authorization.split(": ");
request.setAuthType(authzParts[0]);
}
}
private void content(MockHttpServletRequest result, String charset) {
String requestBody = webRequest.getRequestBody();
if (requestBody == null) {
return;
}
try {
result.setContent(requestBody.getBytes(charset));
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
private void contentType(MockHttpServletRequest result) {
String contentType = header("Content-Type");
result.setContentType(contentType == null ? MediaType.ALL_VALUE.toString() : contentType);
}
private void contextPath(MockHttpServletRequest result, UriComponents uriComponents) {
if (contextPath == null) {
List<String> pathSegments = uriComponents.getPathSegments();
if (pathSegments.isEmpty()) {
result.setContextPath("");
}
else {
result.setContextPath("/" + pathSegments.get(0));
}
}
else {
if (!uriComponents.getPath().startsWith(contextPath)) {
throw new IllegalArgumentException(uriComponents.getPath() + " should start with contextPath "
+ contextPath);
}
result.setContextPath(contextPath);
}
}
private void cookies(MockHttpServletRequest result) {
String cookieHeaderValue = header("Cookie");
Cookie[] parentCookies = result.getCookies();
List<Cookie> cookies = new ArrayList<Cookie>();
if (cookieHeaderValue != null) {
StringTokenizer tokens = new StringTokenizer(cookieHeaderValue, "=;");
while (tokens.hasMoreTokens()) {
String cookieName = tokens.nextToken().trim();
if (!tokens.hasMoreTokens()) {
throw new IllegalArgumentException("Expected value for cookie name " + cookieName
+ ". Full cookie was " + cookieHeaderValue);
}
String cookieValue = tokens.nextToken().trim();
processCookie(result, cookies, new Cookie(cookieName, cookieValue));
}
}
@SuppressWarnings("unchecked")
Set<com.gargoylesoftware.htmlunit.util.Cookie> managedCookies = webClient.getCookies(webRequest.getUrl());
for (com.gargoylesoftware.htmlunit.util.Cookie cookie : managedCookies) {
processCookie(result, cookies, new Cookie(cookie.getName(), cookie.getValue()));
}
if(parentCookies != null) {
for(Cookie cookie : parentCookies) {
cookies.add(cookie);
}
}
if (!cookies.isEmpty()) {
result.setCookies(cookies.toArray(new Cookie[0]));
}
}
private void processCookie(MockHttpServletRequest result, List<Cookie> cookies, Cookie cookie) {
cookies.add(cookie);
if ("JSESSIONID".equals(cookie.getName())) {
result.setRequestedSessionId(cookie.getValue());
result.setSession(httpSession(result, cookie.getValue()));
}
}
private String getCharset() {
String charset = webRequest.getCharset();
if (charset == null) {
return "ISO-8859-1";
}
return charset;
}
private String header(String headerName) {
return webRequest.getAdditionalHeaders().get(headerName);
}
private void headers(MockHttpServletRequest result) {
for (Entry<String, String> header : webRequest.getAdditionalHeaders().entrySet()) {
result.addHeader(header.getKey(), header.getValue());
}
}
private MockHttpSession httpSession(MockHttpServletRequest request, final String sessionid) {
MockHttpSession session;
synchronized (sessions) {
session = sessions.get(sessionid);
if (session == null) {
session = new HtmlUnitMockHttpSession(request, sessionid);
session.setNew(true);
synchronized (sessions) {
sessions.put(sessionid, session);
}
addSessionCookie(request, sessionid);
}
else {
session.setNew(false);
}
}
return session;
}
private void addSessionCookie(MockHttpServletRequest request, String sessionid) {
getCookieManager().addCookie(createCookie(request, sessionid));
}
private void removeSessionCookie(MockHttpServletRequest request, String sessionid) {
getCookieManager().removeCookie(createCookie(request, sessionid));
}
private com.gargoylesoftware.htmlunit.util.Cookie createCookie(MockHttpServletRequest request, String sessionid) {
return new com.gargoylesoftware.htmlunit.util.Cookie(request.getServerName(), "JSESSIONID", sessionid,
request.getContextPath() + "/", null, request.isSecure(), true);
}
private void locales(MockHttpServletRequest result) {
String locale = header("Accept-Language");
if (locale == null) {
result.addPreferredLocale(Locale.getDefault());
}
else {
String[] locales = locale.split(", ");
for (int i = locales.length - 1; i >= 0; i--) {
result.addPreferredLocale(parseLocale(locales[i]));
}
}
}
private void params(MockHttpServletRequest result, UriComponents uriComponents) {
for (Entry<String, List<String>> values : uriComponents.getQueryParams().entrySet()) {
String name = values.getKey();
for (String value : values.getValue()) {
try {
result.addParameter(name, URLDecoder.decode(value, "UTF-8"));
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
}
for (NameValuePair param : webRequest.getRequestParameters()) {
result.addParameter(param.getName(), param.getValue());
}
}
private Locale parseLocale(String locale) {
Matcher matcher = LOCALE_PATTERN.matcher(locale);
if (!matcher.matches()) {
throw new IllegalArgumentException("Invalid locale " + locale);
}
String language = matcher.group(1);
String country = matcher.group(2);
if (country == null) {
country = "";
}
String qualifier = matcher.group(3);
if (qualifier == null) {
qualifier = "";
}
return new Locale(language, country, qualifier);
}
private void pathInfo(UriComponents uriComponents, MockHttpServletRequest result) {
result.setPathInfo(null);
}
private void servletPath(MockHttpServletRequest result, String requestPath) {
String servletPath = requestPath.substring(result.getContextPath().length());
if ("".equals(servletPath)) {
servletPath = null;
}
result.setServletPath(servletPath);
}
private void servletPath(UriComponents uriComponents, MockHttpServletRequest result) {
if ("".equals(result.getPathInfo())) {
result.setPathInfo(null);
}
servletPath(result, uriComponents.getPath());
}
private void ports(UriComponents uriComponents, MockHttpServletRequest result) {
int serverPort = uriComponents.getPort();
result.setServerPort(serverPort);
if (serverPort == -1) {
int portConnection = webRequest.getUrl().getDefaultPort();
result.setLocalPort(serverPort);
result.setRemotePort(portConnection);
}
else {
result.setRemotePort(serverPort);
}
}
private UriComponents uriComponents() {
URL url = webRequest.getUrl();
UriComponentsBuilder uriBldr = UriComponentsBuilder.fromUriString(url.toExternalForm());
return uriBldr.build();
}
@Override
public boolean isMergeEnabled() {
return true;
}
@Override
public Object merge(Object parent) {
if (parent == null) {
return this;
}
if(parent instanceof RequestBuilder) {
this.parentBuilder = (RequestBuilder) parent;
}
if (parent instanceof SmartRequestBuilder) {
this.parentPostProcessor = (SmartRequestBuilder) parent;
}
return this;
}
/**
* An extension to {@link MockHttpServletRequest} that ensures that when a new {@link HttpSession} is created, it is
* added to the managed sessions.
*
* @author Rob Winch
*/
private final class HtmlUnitMockHttpServletRequest extends MockHttpServletRequest {
private HtmlUnitMockHttpServletRequest(ServletContext servletContext, String method, String requestURI) {
super(servletContext, method, requestURI);
}
public HttpSession getSession(boolean create) {
HttpSession result = super.getSession(false);
if (result == null && create) {
HtmlUnitMockHttpSession newSession = new HtmlUnitMockHttpSession(this);
setSession(newSession);
newSession.setNew(true);
String sessionid = newSession.getId();
synchronized (sessions) {
sessions.put(sessionid, newSession);
}
addSessionCookie(this, sessionid);
result = newSession;
}
return result;
}
public HttpSession getSession() {
return super.getSession();
}
public void setSession(HttpSession session) {
super.setSession(session);
}
}
/**
* An extension to {@link MockHttpSession} that ensures when {@link #invalidate()} is called that the
* {@link HttpSession} is removed from the managed sessions.
*
* @author Rob Winch
*/
private final class HtmlUnitMockHttpSession extends MockHttpSession {
private final MockHttpServletRequest request;
private HtmlUnitMockHttpSession(MockHttpServletRequest request) {
super(request.getServletContext());
this.request = request;
}
private HtmlUnitMockHttpSession(MockHttpServletRequest request, String id) {
super(request.getServletContext(), id);
this.request = request;
}
public void invalidate() {
super.invalidate();
synchronized (sessions) {
sessions.remove(getId());
}
removeSessionCookie(request, getId());
}
}
private CookieManager getCookieManager() {
return webClient.getCookieManager();
}
private static final Pattern LOCALE_PATTERN = Pattern.compile("^\\s*(\\w{2})(?:-(\\w{2}))?(?:;q=(\\d+\\.\\d+))?$");
}

97
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java

@ -0,0 +1,97 @@ @@ -0,0 +1,97 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import com.gargoylesoftware.htmlunit.WebClient;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.web.context.WebApplicationContext;
/**
* Simplifies creating a WebClient that delegates to a MockMvc instance.
*
* @author Rob Winch
* @since 4.2
*/
public class MockMvcWebClientBuilder extends MockMvcWebConnectionBuilderSupport<MockMvcWebClientBuilder> {
protected MockMvcWebClientBuilder(MockMvc mockMvc) {
super(mockMvc);
}
protected MockMvcWebClientBuilder(WebApplicationContext context) {
super(context);
}
protected MockMvcWebClientBuilder(WebApplicationContext context, MockMvcConfigurer configurer) {
super(context, configurer);
}
/**
* Creates a new instance with a WebApplicationContext.
*
* @param context the WebApplicationContext to use. Cannot be null.
* @return the MockMvcWebClientBuilder to customize
*/
public static MockMvcWebClientBuilder webAppContextSetup(WebApplicationContext context) {
return new MockMvcWebClientBuilder(context);
}
/**
* Creates a new instance using a WebApplicationContext
* @param context the WebApplicationContext to create a MockMvc instance from.
* @param configurer the MockMvcConfigurer to apply
* Cannot be null.
* @return the MockMvcWebClientBuilder to use
*/
public static MockMvcWebClientBuilder webAppContextSetup(WebApplicationContext context, MockMvcConfigurer configurer) {
return new MockMvcWebClientBuilder(context, configurer);
}
/**
* Creates a new instance with a MockMvc instance.
*
* @param mockMvc the MockMvc to use. Cannot be null.
* @return the MockMvcWebClientBuilder to customize
*/
public static MockMvcWebClientBuilder mockMvcSetup(MockMvc mockMvc) {
return new MockMvcWebClientBuilder(mockMvc);
}
/**
* Creates a WebClient that uses the provided MockMvc for any matching requests and a
* WebClient with all the default settings for any other request.
*
* @return the WebClient to use
*/
public WebClient createWebClient() {
return configureWebClient(new WebClient());
}
/**
* Creates a WebClient that uses the provided MockMvc for any matching requests and the
* provided WebClient for any other request.
*
* @param webClient The WebClient to delegate to for requests that do not match. Cannot be null.
*
* @return the WebClient to use
*/
public WebClient configureWebClient(WebClient webClient) {
webClient.setWebConnection(createConnection(webClient.getWebConnection()));
return webClient;
}
}

150
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java

@ -0,0 +1,150 @@ @@ -0,0 +1,150 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import com.gargoylesoftware.htmlunit.CookieManager;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebConnection;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.htmlunit.webdriver.WebConnectionHtmlUnitDriver;
import org.springframework.util.Assert;
/**
* <p>
* Allows {@link MockMvc} to transform a {@link WebRequest} into a {@link WebResponse}. This is the core integration
* with <a href="http://htmlunit.sourceforge.net/">HTML Unit</a>.
* </p>
* <p>
* Example usage can be seen below:
* </p>
*
* <pre>
* WebClient webClient = new WebClient();
* MockMvc mockMvc = ...
* MockMvcWebConnection webConnection = new MockMvcWebConnection(mockMvc);
* mockConnection.setWebClient(webClient);
* webClient.setWebConnection(webConnection);
*
* ... use webClient as normal ...
* </pre>
*
* @author Rob Winch
* @since 4.2
* @see WebConnectionHtmlUnitDriver
*/
public final class MockMvcWebConnection implements WebConnection {
private WebClient webClient;
private final Map<String, MockHttpSession> sessions = new HashMap<String, MockHttpSession>();
private final MockMvc mockMvc;
private final String contextPath;
/**
* Creates a new instance that assumes the context root of the application is "". For example,
* the URL http://localhost/test/this would use "" as the context root.
*
* @param mockMvc the MockMvc instance to use
*/
public MockMvcWebConnection(MockMvc mockMvc) {
this(mockMvc, "");
}
/**
* Creates a new instance with a specified context root.
*
* @param mockMvc the MockMvc instance to use
* @param contextPath the contextPath to use. The value may be null in which case the first path segment of the URL is turned
* into the contextPath. Otherwise it must conform to {@link HttpServletRequest#getContextPath()} which states it
* can be empty string or it must start with a "/" and not end in a "/".
*/
public MockMvcWebConnection(MockMvc mockMvc, String contextPath) {
Assert.notNull(mockMvc, "mockMvc cannot be null");
validateContextPath(contextPath);
this.webClient = new WebClient();
this.mockMvc = mockMvc;
this.contextPath = contextPath;
}
public WebResponse getResponse(WebRequest webRequest) throws IOException {
long startTime = System.currentTimeMillis();
HtmlUnitRequestBuilder requestBuilder = new HtmlUnitRequestBuilder(sessions, webClient, webRequest);
requestBuilder.setContextPath(contextPath);
MockHttpServletResponse httpServletResponse = getResponse(requestBuilder);
String forwardedUrl = httpServletResponse.getForwardedUrl();
while(forwardedUrl != null) {
requestBuilder.setForwardPostProcessor(new ForwardRequestPostProcessor(forwardedUrl));
httpServletResponse = getResponse(requestBuilder);
forwardedUrl = httpServletResponse.getForwardedUrl();
}
return new MockWebResponseBuilder(startTime, webRequest, httpServletResponse).build();
}
public void setWebClient(WebClient webClient) {
Assert.notNull(webClient, "webClient cannot be null");
this.webClient = webClient;
}
private CookieManager getCookieManager() {
return webClient.getCookieManager();
}
private MockHttpServletResponse getResponse(RequestBuilder requestBuilder) throws IOException {
ResultActions resultActions;
try {
resultActions = mockMvc.perform(requestBuilder);
}
catch (Exception e) {
throw (IOException) new IOException(e.getMessage()).initCause(e);
}
return resultActions.andReturn().getResponse();
}
/**
* Performs validation on the contextPath
*
* @param contextPath the contextPath to validate
*/
private static void validateContextPath(String contextPath) {
if (contextPath == null || "".equals(contextPath)) {
return;
}
if (contextPath.endsWith("/")) {
throw new IllegalArgumentException("contextPath cannot end with /. Got '" + contextPath + "'");
}
if (!contextPath.startsWith("/")) {
throw new IllegalArgumentException("contextPath must start with /. Got '" + contextPath + "'");
}
}
}

156
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java

@ -0,0 +1,156 @@ @@ -0,0 +1,156 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.util.ArrayList;
import java.util.List;
import com.gargoylesoftware.htmlunit.WebConnection;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
/**
* Makes it easy to create a WebConnection that uses MockMvc and optionally delegates to
* a real WebConnection for specific requests. The default is to use MockMvc for any host
* that is "localhost" and otherwise use a real WebConnection.
*
* @author Rob Winch
* @since 4.2
*/
public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebConnectionBuilderSupport<T>> {
private String contextPath = "";
private final MockMvc mockMvc;
private List<WebRequestMatcher> mockMvcRequestMatchers = new ArrayList<WebRequestMatcher>();
private boolean alwaysUseMockMvc;
/**
* Creates a new instance using a MockMvc instance
*
* @param mockMvc the MockMvc instance to use. Cannot be null.
*/
protected MockMvcWebConnectionBuilderSupport(MockMvc mockMvc) {
Assert.notNull(mockMvc, "mockMvc cannot be null");
this.mockMvc = mockMvc;
this.mockMvcRequestMatchers.add(new HostRequestMatcher("localhost"));
}
/**
* Creates a new instance using a WebApplicationContext
* @param context the WebApplicationContext to create a MockMvc instance from.
* Cannot be null.
*/
protected MockMvcWebConnectionBuilderSupport(WebApplicationContext context) {
this(MockMvcBuilders.webAppContextSetup(context).build());
}
/**
* Creates a new instance using a WebApplicationContext
* @param context the WebApplicationContext to create a MockMvc instance from.
* @param configurer the MockMvcConfigurer to apply
* Cannot be null.
*/
protected MockMvcWebConnectionBuilderSupport(WebApplicationContext context, MockMvcConfigurer configurer) {
this(MockMvcBuilders.webAppContextSetup(context).apply(configurer).build());
}
/**
* The context path to use. Default is "". If the value is null, then the first path
* segment of the request URL is assumed to be the context path.
*
* @param contextPath the context path to use.
* @return the builder for further customization
*/
@SuppressWarnings("unchecked")
public T contextPath(String contextPath) {
this.contextPath = contextPath;
return (T) this;
}
/**
* Always use MockMvc no matter what the request looks like.
*
* @return the builder for further customization
*/
@SuppressWarnings("unchecked")
public T alwaysUseMockMvc() {
this.alwaysUseMockMvc = true;
return (T) this;
}
/**
* Add additional WebRequestMatcher instances that if return true will ensure MockMvc
* is used.
*
* @param matchers the WebRequestMatcher instances that if true will ensure MockMvc
* processes the request.
* @return the builder for further customization
*/
@SuppressWarnings("unchecked")
public T useMockMvc(WebRequestMatcher... matchers) {
for(WebRequestMatcher matcher : matchers) {
this.mockMvcRequestMatchers.add(matcher);
}
return (T) this;
}
/**
* Add additional WebRequestMatcher instances that will return true if the host matches.
*
* @param hosts the additional hosts that will ensure MockMvc gets invoked (i.e. example.com or example.com:8080).
* @return the builder for further customization
*/
@SuppressWarnings("unchecked")
public T useMockMvcForHosts(String... hosts) {
this.mockMvcRequestMatchers.add(new HostRequestMatcher(hosts));
return (T) this;
}
/**
* Creates a new WebConnection that will use a MockMvc instance if one of the
* specified WebRequestMatcher matches.
*
* @param defaultConnection the default WebConnection to use if none of the specified
* WebRequestMatcher instances match. Cannot be null.
* @return a new WebConnection that will use a MockMvc instance if one of the
* specified WebRequestMatcher matches.
*
* @see #alwaysUseMockMvc
* @see #useMockMvc(WebRequestMatcher...)
* @see #useMockMvcForHosts(String...)
*/
protected final WebConnection createConnection(WebConnection defaultConnection) {
Assert.notNull(defaultConnection, "defaultConnection cannot be null");
MockMvcWebConnection mockMvcWebConnection = new MockMvcWebConnection(mockMvc, contextPath);
if(alwaysUseMockMvc) {
return mockMvcWebConnection;
}
List<DelegatingWebConnection.DelegateWebConnection> delegates = new ArrayList<DelegatingWebConnection.DelegateWebConnection>(mockMvcRequestMatchers.size());
for(WebRequestMatcher matcher : mockMvcRequestMatchers) {
delegates.add(new DelegatingWebConnection.DelegateWebConnection(matcher, mockMvcWebConnection));
}
return new DelegatingWebConnection(defaultConnection, delegates);
}
}

93
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java

@ -0,0 +1,93 @@ @@ -0,0 +1,93 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.WebResponseData;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.util.Assert;
/**
* @author Rob Winch
* @since 4.2
*/
final class MockWebResponseBuilder {
private final long startTime;
private final WebRequest webRequest;
private final MockHttpServletResponse response;
public MockWebResponseBuilder(long startTime, WebRequest webRequest, MockHttpServletResponse httpServletResponse) {
Assert.notNull(webRequest, "webRequest");
Assert.notNull(httpServletResponse, "httpServletResponse cannot be null");
this.startTime = startTime;
this.webRequest = webRequest;
this.response = httpServletResponse;
}
public WebResponse build() throws IOException {
WebResponseData webResponseData = webResponseData();
long endTime = System.currentTimeMillis();
return new WebResponse(webResponseData, webRequest, endTime - startTime);
}
private WebResponseData webResponseData() throws IOException {
List<NameValuePair> responseHeaders = responseHeaders();
int statusCode = response.getRedirectedUrl() == null ? response.getStatus() : 301;
String statusMessage = statusMessage(statusCode);
return new WebResponseData(response.getContentAsByteArray(), statusCode, statusMessage, responseHeaders);
}
private String statusMessage(int statusCode) {
String errorMessage = response.getErrorMessage();
if (errorMessage != null) {
return errorMessage;
}
try {
return HttpStatus.valueOf(statusCode).getReasonPhrase();
}
catch (IllegalArgumentException useDefault) {
}
;
return "N/A";
}
private List<NameValuePair> responseHeaders() {
Collection<String> headerNames = response.getHeaderNames();
List<NameValuePair> responseHeaders = new ArrayList<NameValuePair>(headerNames.size());
for (String headerName : headerNames) {
List<Object> headerValues = response.getHeaderValues(headerName);
for (Object value : headerValues) {
responseHeaders.add(new NameValuePair(headerName, String.valueOf(value)));
}
}
String location = response.getRedirectedUrl();
if (location != null) {
responseHeaders.add(new NameValuePair("Location", location));
}
return responseHeaders;
}
}

50
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcher.java

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.util.regex.Pattern;
import com.gargoylesoftware.htmlunit.WebRequest;
/**
* <p>
* An implementation of WebRequestMatcher that allows matching on WebRequest#getUrl().toExternalForm() using a regular expression. For example, if you would like to match on the domain code.jquery.com, you might want to use the following:</p>
*
* <pre>
* WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*");
* </pre>
*
* @author Rob Winch
* @since 4.2
* @see org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection
*/
public final class UrlRegexRequestMatcher implements WebRequestMatcher {
private Pattern pattern;
public UrlRegexRequestMatcher(String regex) {
pattern = Pattern.compile(regex);
}
public UrlRegexRequestMatcher(Pattern pattern) {
this.pattern = pattern;
}
@Override
public boolean matches(WebRequest request) {
String url = request.getUrl().toExternalForm();
return pattern.matcher(url).matches();
}
}

34
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/WebRequestMatcher.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import com.gargoylesoftware.htmlunit.WebRequest;
/**
* Strategy to match on a WebRequest
*
* @author Rob Winch
* @since 4.2
*/
public interface WebRequestMatcher {
/**
* Return true if matches on WebRequest, else false
*
* @param request the WebRequest to attempt to match on
* @return true if matches on WebRequest, else false
*/
boolean matches(WebRequest request);
}

7
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/package-info.java

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
/**
* Support for MockMvc and HtmlUnit integration
*
* @author Rob Winch
* @since 4.2
*/
package org.springframework.test.web.servlet.htmlunit;

114
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java

@ -0,0 +1,114 @@ @@ -0,0 +1,114 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit.webdriver;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.htmlunit.MockMvcWebConnectionBuilderSupport;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.web.context.WebApplicationContext;
/**
* Convenience class for building an HtmlUnitDriver that will delegate to MockMvc and
* optionally delegate to an actual connection for specific requests.
*
* By default localhost will delegate to MockMvc and any other URL will delegate
*
* @author Rob Winch
* @since 4.2
*/
public class MockMvcHtmlUnitDriverBuilder extends MockMvcWebConnectionBuilderSupport<MockMvcHtmlUnitDriverBuilder> {
private boolean javascriptEnabled = true;
protected MockMvcHtmlUnitDriverBuilder(MockMvc mockMvc) {
super(mockMvc);
}
protected MockMvcHtmlUnitDriverBuilder(WebApplicationContext context) {
super(context);
}
protected MockMvcHtmlUnitDriverBuilder(WebApplicationContext context, MockMvcConfigurer configurer) {
super(context, configurer);
}
/**
* Creates a new instance with a WebApplicationContext.
*
* @param context the WebApplicationContext to use. Cannot be null.
* @return the MockMvcHtmlUnitDriverBuilder to customize
*/
public static MockMvcHtmlUnitDriverBuilder webAppContextSetup(WebApplicationContext context) {
return new MockMvcHtmlUnitDriverBuilder(context);
}
/**
* Creates a new instance using a WebApplicationContext
* @param context the WebApplicationContext to create a MockMvc instance from.
* @param configurer the MockMvcConfigurer to apply
* Cannot be null.
* @return the MockMvcHtmlUnitDriverBuilder to customize
*/
public static MockMvcHtmlUnitDriverBuilder webAppContextSetup(WebApplicationContext context, MockMvcConfigurer configurer) {
return new MockMvcHtmlUnitDriverBuilder(context, configurer);
}
/**
* Creates a new instance with a MockMvc instance.
*
* @param mockMvc the MockMvc to use. Cannot be null.
* @return the MockMvcHtmlUnitDriverBuilder to customize
*/
public static MockMvcHtmlUnitDriverBuilder mockMvcSetup(MockMvc mockMvc) {
return new MockMvcHtmlUnitDriverBuilder(mockMvc);
}
/**
* Specifies if JavaScript should be enabled or not. Default is true.
*
* @param javascriptEnabled if JavaScript should be enabled or not.
* @return the builder for further customizations
*/
public MockMvcHtmlUnitDriverBuilder javascriptEnabled(boolean javascriptEnabled) {
this.javascriptEnabled = javascriptEnabled;
return this;
}
/**
* Creates a new HtmlUnitDriver with the BrowserVersion set to CHROME. For additional
* configuration options, use configureDriver.
*
* @return the HtmlUnitDriver to use
* @see #configureDriver(WebConnectionHtmlUnitDriver)
*/
public HtmlUnitDriver createDriver() {
return configureDriver(new WebConnectionHtmlUnitDriver(BrowserVersion.CHROME));
}
/**
* Configures an existing WebConnectionHtmlUnitDriver.
*
* @param driver the WebConnectionHtmlUnitDriver to configure
* @return the HtmlUnitDriver to use
*/
public HtmlUnitDriver configureDriver(WebConnectionHtmlUnitDriver driver) {
driver.setJavascriptEnabled(javascriptEnabled);
driver.setWebConnection(createConnection(driver.getWebConnection()));
return driver;
}
}

96
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java

@ -0,0 +1,96 @@ @@ -0,0 +1,96 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit.webdriver;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebConnection;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.springframework.util.Assert;
/**
* <p>
* Allows configuring the WebConnection for an HtmlUnitDriver instance. This is useful
* because it allows a MockMvcWebConnection to be injected.
* </p>
*
* @author Rob Winch
* @since 4.2
* @see MockMvcHtmlUnitDriverBuilder
*/
public class WebConnectionHtmlUnitDriver extends HtmlUnitDriver {
private WebClient webClient;
public WebConnectionHtmlUnitDriver(BrowserVersion version) {
super(version);
}
public WebConnectionHtmlUnitDriver() {
}
public WebConnectionHtmlUnitDriver(boolean enableJavascript) {
super(enableJavascript);
}
public WebConnectionHtmlUnitDriver(Capabilities capabilities) {
super(capabilities);
}
/**
* Captures the WebClient that is used so that its WebConnection is accessible.
*
* @param client The client to modify
* @return The modified client
*/
@Override
protected final WebClient modifyWebClient(WebClient client) {
webClient = super.modifyWebClient(client);
webClient = configureWebClient(webClient);
return webClient;
}
/**
* Subclasses can override this method to customise the WebClient that the HtmlUnit
* driver uses.
*
* @param client The client to modify
* @return The modified client
*/
protected WebClient configureWebClient(WebClient client) {
return client;
}
/**
* Allows accessing the current WebConnection
*
* @return the current WebConnection
*/
public WebConnection getWebConnection() {
return webClient.getWebConnection();
}
/**
* Sets the WebConnection to be used.
*
* @param webConnection the WebConnection to use. Cannot be null.
*/
public void setWebConnection(WebConnection webConnection) {
Assert.notNull(webConnection, "webConnection cannot be null");
this.webClient.setWebConnection(webConnection);
}
}

7
spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/package-info.java

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
/**
* Support for MockMvc and HtmlUnitDriver
*
* @author Rob Winch
* @since 4.2
*/
package org.springframework.test.web.servlet.htmlunit.webdriver;

137
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java

@ -0,0 +1,137 @@ @@ -0,0 +1,137 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.net.URL;
import java.util.Collections;
import com.gargoylesoftware.htmlunit.HttpWebConnection;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebConnection;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.WebResponseData;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.Matchers.isEmptyString;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import static org.mockito.Mockito.*;
import org.mockito.runners.MockitoJUnitRunner;
import static org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection.DelegateWebConnection;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
/**
* @author Rob Winch
*/
@RunWith(MockitoJUnitRunner.class)
public class DelegatingWebConnectionTests {
private DelegatingWebConnection webConnection;
@Mock
private WebRequestMatcher matcher1;
@Mock
private WebRequestMatcher matcher2;
@Mock
private WebConnection defaultConnection;
@Mock
private WebConnection connection1;
@Mock
private WebConnection connection2;
private WebRequest request;
private WebResponse expectedResponse;
@Before
public void setUp() throws Exception {
request = new WebRequest(new URL("http://localhost/"));
WebResponseData data = new WebResponseData("".getBytes("UTF-8"),200, "", Collections.<NameValuePair>emptyList());
expectedResponse = new WebResponse(data, request, 100L);
webConnection = new DelegatingWebConnection(defaultConnection, new DelegateWebConnection(matcher1,connection1), new DelegateWebConnection(matcher2,connection2));
}
@Test
public void getResponseDefault() throws Exception {
when(defaultConnection.getResponse(request)).thenReturn(expectedResponse);
WebResponse response = webConnection.getResponse(request);
assertThat(response, sameInstance(expectedResponse));
verify(matcher1).matches(request);
verify(matcher2).matches(request);
verifyNoMoreInteractions(connection1,connection2);
verify(defaultConnection).getResponse(request);
}
@Test
public void getResponseAllMatches() throws Exception {
when(matcher1.matches(request)).thenReturn(true);
when(matcher2.matches(request)).thenReturn(true);
when(connection1.getResponse(request)).thenReturn(expectedResponse);
WebResponse response = webConnection.getResponse(request);
assertThat(response, sameInstance(expectedResponse));
verify(matcher1).matches(request);
verifyNoMoreInteractions(matcher2,connection2,defaultConnection);
verify(connection1).getResponse(request);
}
@Test
public void getResponseSecondMatches() throws Exception {
when(matcher2.matches(request)).thenReturn(true);
when(connection2.getResponse(request)).thenReturn(expectedResponse);
WebResponse response = webConnection.getResponse(request);
assertThat(response, sameInstance(expectedResponse));
verify(matcher1).matches(request);
verify(matcher2).matches(request);
verifyNoMoreInteractions(connection1,defaultConnection);
verify(connection2).getResponse(request);
}
@Test
public void classlevelJavadoc() throws Exception {
WebClient webClient = new WebClient();
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(TestController.class).build();
MockMvcWebConnection mockConnection = new MockMvcWebConnection(mockMvc);
mockConnection.setWebClient(webClient);
WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*");
WebConnection httpConnection = new HttpWebConnection(webClient);
WebConnection webConnection = new DelegatingWebConnection(mockConnection, new DelegateWebConnection(cdnMatcher, httpConnection));
webClient.setWebConnection(webConnection);
Page page = webClient.getPage("http://code.jquery.com/jquery-1.11.0.min.js");
assertThat(page.getWebResponse().getStatusCode(), equalTo(200));
assertThat(page.getWebResponse().getContentAsString(), not(isEmptyString()));
}
@Controller
static class TestController {}
}

31
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/ForwardController.java

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author Rob Winch
*/
@Controller
public class ForwardController {
@RequestMapping("/forward")
public String forward() {
return "forward:/";
}
}

34
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HelloController.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @author Rob Winch
*/
@Controller
public class HelloController {
@RequestMapping
@ResponseBody
public String header(HttpServletRequest request) {
return "hello";
}
}

79
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcherTests.java

@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.net.URL;
import com.gargoylesoftware.htmlunit.WebRequest;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import org.junit.Test;
import org.springframework.test.web.servlet.htmlunit.HostRequestMatcher;
import org.springframework.test.web.servlet.htmlunit.WebRequestMatcher;
/**
* @author Rob Winch
*/
public class HostRequestMatcherTests {
@Test
public void localhostMatches() throws Exception {
WebRequestMatcher matcher = new HostRequestMatcher("localhost");
boolean matches = matcher.matches(new WebRequest(new URL("http://localhost/jquery-1.11.0.min.js")));
assertThat(matches, equalTo(true));;
matches = matcher.matches(new WebRequest(new URL("http://example.com/jquery-1.11.0.min.js")));
assertThat(matches, equalTo(false));;
}
@Test
public void multipleHosts() throws Exception {
WebRequestMatcher matcher = new HostRequestMatcher("localhost","example.com");
boolean matches = matcher.matches(new WebRequest(new URL("http://localhost/jquery-1.11.0.min.js")));
assertThat(matches, equalTo(true));;
matches = matcher.matches(new WebRequest(new URL("http://example.com/jquery-1.11.0.min.js")));
assertThat(matches, equalTo(true));;
}
@Test
public void specificPort() throws Exception {
WebRequestMatcher matcher = new HostRequestMatcher("localhost:8080");
boolean matches = matcher.matches(new WebRequest(new URL("http://localhost:8080/jquery-1.11.0.min.js")));
assertThat(matches, equalTo(true));;
matches = matcher.matches(new WebRequest(new URL("http://localhost:9090/jquery-1.11.0.min.js")));
assertThat(matches, equalTo(false));;
}
@Test
public void defaultPortInMatcher() throws Exception {
WebRequestMatcher matcher = new HostRequestMatcher("localhost:80");
boolean matches = matcher.matches(new WebRequest(new URL("http://localhost:80/jquery-1.11.0.min.js")));
assertThat(matches, equalTo(true));;
matches = matcher.matches(new WebRequest(new URL("http://localhost/jquery-1.11.0.min.js")));
assertThat(matches, equalTo(true));;
matches = matcher.matches(new WebRequest(new URL("http://localhost:9090/jquery-1.11.0.min.js")));
assertThat(matches, equalTo(false));;
}
}

782
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java

@ -0,0 +1,782 @@ @@ -0,0 +1,782 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;
import com.gargoylesoftware.htmlunit.CookieManager;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import org.apache.commons.io.IOUtils;
import org.apache.http.auth.UsernamePasswordCredentials;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
/**
*
* @author Rob Winch
*
*/
public class HtmlUnitRequestBuilderTests {
private WebRequest webRequest;
private ServletContext servletContext;
private Map<String, MockHttpSession> sessions;
private WebClient webClient;
private HtmlUnitRequestBuilder requestBuilder;
@Before
public void setUp() throws Exception {
sessions = new HashMap<>();
webRequest = new WebRequest(new URL("http://example.com:80/test/this/here"));
webRequest.setHttpMethod(HttpMethod.GET);
webRequest.setRequestParameters(new ArrayList<>());
webClient = new WebClient();
requestBuilder = new HtmlUnitRequestBuilder(sessions, webClient, webRequest);
servletContext = new MockServletContext();
}
// --- constructor
@Test(expected = IllegalArgumentException.class)
public void constructorNullSessions() {
new HtmlUnitRequestBuilder(null, webClient, webRequest);
}
@Test(expected = IllegalArgumentException.class)
public void constructorNullWebClient() {
new HtmlUnitRequestBuilder(sessions, null, webRequest);
}
@Test(expected = IllegalArgumentException.class)
public void constructorNullWebRequest() {
new HtmlUnitRequestBuilder(sessions, webClient, null);
}
// --- buildRequest
@Test
public void buildRequestBasicAuth() {
String base64Credentials = "dXNlcm5hbWU6cGFzc3dvcmQ=";
String authzHeaderValue = "Basic: " + base64Credentials;
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(base64Credentials);
webRequest.setCredentials(credentials);
webRequest.setAdditionalHeader("Authorization", authzHeaderValue);
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getAuthType(), equalTo("Basic"));
assertThat(actualRequest.getHeader("Authorization"), equalTo(authzHeaderValue));
}
@Test
public void buildRequestCharacterEncoding() {
String charset = "UTF-8";
webRequest.setCharset(charset);
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getCharacterEncoding(), equalTo(charset));
}
@Test
public void buildRequestDefaultCharacterEncoding() {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getCharacterEncoding(), equalTo("ISO-8859-1"));
}
@Test
public void buildRequestContentLength() {
String content = "some content that has length";
webRequest.setHttpMethod(HttpMethod.POST);
webRequest.setRequestBody(content);
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getContentLength(), equalTo(content.length()));
}
@Test
public void buildRequestContentType() {
String contentType = "text/html;charset=UTF-8";
webRequest.setAdditionalHeader("Content-Type", contentType);
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getContentType(), equalTo(contentType));
assertThat(actualRequest.getHeader("Content-Type"), equalTo(contentType));
}
@Test
public void buildRequestContextPathUsesFirstSegmentByDefault() {
String contextPath = requestBuilder.buildRequest(servletContext).getContextPath();
assertThat(contextPath, equalTo("/test"));
}
@Test
public void buildRequestContextPathUsesNoFirstSegmentWithDefault() throws MalformedURLException {
webRequest.setUrl(new URL("http://example.com/"));
String contextPath = requestBuilder.buildRequest(servletContext).getContextPath();
assertThat(contextPath, equalTo(""));
}
@Test(expected = IllegalArgumentException.class)
public void buildRequestContextPathInvalid() {
requestBuilder.setContextPath("/invalid");
requestBuilder.buildRequest(servletContext).getContextPath();
}
@Test
public void buildRequestContextPathEmpty() {
String expected = "";
requestBuilder.setContextPath(expected);
String contextPath = requestBuilder.buildRequest(servletContext).getContextPath();
assertThat(contextPath, equalTo(expected));
}
@Test
public void buildRequestContextPathExplicit() {
String expected = "/test";
requestBuilder.setContextPath(expected);
String contextPath = requestBuilder.buildRequest(servletContext).getContextPath();
assertThat(contextPath, equalTo(expected));
}
@Test
public void buildRequestContextPathMulti() {
String expected = "/test/this";
requestBuilder.setContextPath(expected);
String contextPath = requestBuilder.buildRequest(servletContext).getContextPath();
assertThat(contextPath, equalTo(expected));
}
@Test
public void buildRequestCookiesNull() {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getCookies(), nullValue());
}
@Test
public void buildRequestCookiesSingle() {
webRequest.setAdditionalHeader("Cookie", "name=value");
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
Cookie[] cookies = actualRequest.getCookies();
assertThat(cookies.length, equalTo(1));
assertThat(cookies[0].getName(), equalTo("name"));
assertThat(cookies[0].getValue(), equalTo("value"));
}
@Test
public void buildRequestCookiesMulti() {
webRequest.setAdditionalHeader("Cookie", "name=value; name2=value2");
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
Cookie[] cookies = actualRequest.getCookies();
assertThat(cookies.length, equalTo(2));
Cookie cookie = cookies[0];
assertThat(cookie.getName(), equalTo("name"));
assertThat(cookie.getValue(), equalTo("value"));
cookie = cookies[1];
assertThat(cookie.getName(), equalTo("name2"));
assertThat(cookie.getValue(), equalTo("value2"));
}
@Test
public void buildRequestInputStream() throws Exception {
String content = "some content that has length";
webRequest.setHttpMethod(HttpMethod.POST);
webRequest.setRequestBody(content);
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(IOUtils.toString(actualRequest.getInputStream()), equalTo(content));
}
@Test
public void buildRequestLocalAddr() {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getLocalAddr(), equalTo("127.0.0.1"));
}
@Test
public void buildRequestLocaleDefault() {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getLocale(), equalTo(Locale.getDefault()));
}
@Test
public void buildRequestLocaleDa() {
webRequest.setAdditionalHeader("Accept-Language", "da");
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getLocale(), equalTo(new Locale("da")));
}
@Test
public void buildRequestLocaleEnGbQ08() {
webRequest.setAdditionalHeader("Accept-Language", "en-gb;q=0.8");
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getLocale(), equalTo(new Locale("en", "gb", "0.8")));
}
@Test
public void buildRequestLocaleEnQ07() {
webRequest.setAdditionalHeader("Accept-Language", "en;q=0.7");
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getLocale(), equalTo(new Locale("en", "", "0.7")));
}
@Test
public void buildRequestLocaleEnUs() {
webRequest.setAdditionalHeader("Accept-Language", "en-US");
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getLocale(), equalTo(Locale.US));
}
@Test
public void buildRequestLocaleFr() {
webRequest.setAdditionalHeader("Accept-Language", "fr");
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getLocale(), equalTo(Locale.FRENCH));
}
@Test
public void buildRequestLocaleMulti() {
webRequest.setAdditionalHeader("Accept-Language", "da, en-gb;q=0.8, en;q=0.7");
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
// FIXME Locale.ENGLISH is due to fact cannot remove it from MockHttpServletRequest
List<Locale> expected = Arrays.asList(new Locale("da"), new Locale("en", "gb", "0.8"), new Locale("en", "",
"0.7"), Locale.ENGLISH);
assertThat(Collections.list(actualRequest.getLocales()), equalTo(expected));
}
@Test
public void buildRequestLocaleName() {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getLocalName(), equalTo("localhost"));
}
@Test
public void buildRequestLocalPort() {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getLocalPort(), equalTo(80));
}
@Test
public void buildRequestLocalMissing() throws Exception {
webRequest.setUrl(new URL("http://localhost/test/this"));
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getLocalPort(), equalTo(-1));
}
@Test
public void buildRequestMethods() {
HttpMethod[] methods = HttpMethod.values();
for (HttpMethod expectedMethod : methods) {
webRequest.setHttpMethod(expectedMethod);
String actualMethod = requestBuilder.buildRequest(servletContext).getMethod();
assertThat(actualMethod, equalTo(actualMethod));
}
}
@Test
public void buildRequestParameterMap() throws Exception {
setParameter("name", "value");
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getParameterMap().size(), equalTo(1));
assertThat(actualRequest.getParameter("name"), equalTo("value"));
}
@Test
public void buildRequestParameterMapQuery() throws Exception {
webRequest.setUrl(new URL("http://example.com/example/?name=value"));
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getParameterMap().size(), equalTo(1));
assertThat(actualRequest.getParameter("name"), equalTo("value"));
}
@Test
public void buildRequestParameterMapQueryMulti() throws Exception {
webRequest.setUrl(new URL("http://example.com/example/?name=value&param2=value+2"));
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getParameterMap().size(), equalTo(2));
assertThat(actualRequest.getParameter("name"), equalTo("value"));
assertThat(actualRequest.getParameter("param2"), equalTo("value 2"));
}
@Test
public void buildRequestPathInfo() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getPathInfo(), nullValue());
}
@Test
public void buildRequestPathInfoNull() throws Exception {
webRequest.setUrl(new URL("http://example.com/example"));
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getPathInfo(), nullValue());
}
@Test
public void buildRequestAndAntPathRequestMatcher() throws Exception {
webRequest.setUrl(new URL("http://example.com/app/login/authenticate"));
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
// verify it is going to work with Spring Security's AntPathRequestMatcher
assertThat(actualRequest.getPathInfo(), nullValue());
assertThat(actualRequest.getServletPath(), equalTo("/login/authenticate"));
}
@Test
public void buildRequestProtocol() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getProtocol(), equalTo("HTTP/1.1"));
}
@Test
public void buildRequestQuery() throws Exception {
String expectedQuery = "aparam=avalue";
webRequest.setUrl(new URL("http://example.com/example?" + expectedQuery));
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getQueryString(), equalTo(expectedQuery));
}
@Test
public void buildRequestReader() throws Exception {
String expectedBody = "request body";
webRequest.setHttpMethod(HttpMethod.POST);
webRequest.setRequestBody(expectedBody);
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(IOUtils.toString(actualRequest.getReader()), equalTo(expectedBody));
}
@Test
public void buildRequestRemoteAddr() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getRemoteAddr(), equalTo("127.0.0.1"));
}
@Test
public void buildRequestRemoteHost() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getRemoteAddr(), equalTo("127.0.0.1"));
}
@Test
public void buildRequestRemotePort() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getRemotePort(), equalTo(80));
}
@Test
public void buildRequestRemotePort8080() throws Exception {
webRequest.setUrl(new URL("http://example.com:8080/"));
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getRemotePort(), equalTo(8080));
}
@Test
public void buildRequestRemotePort80WithDefault() throws Exception {
webRequest.setUrl(new URL("http://example.com/"));
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getRemotePort(), equalTo(80));
}
@Test
public void buildRequestRequestedSessionId() throws Exception {
String sessionId = "session-id";
webRequest.setAdditionalHeader("Cookie", "JSESSIONID=" + sessionId);
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getRequestedSessionId(), equalTo(sessionId));
}
@Test
public void buildRequestRequestedSessionIdNull() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getRequestedSessionId(), nullValue());
}
@Test
public void buildRequestUri() {
String uri = requestBuilder.buildRequest(servletContext).getRequestURI();
assertThat(uri, equalTo("/test/this/here"));
}
@Test
public void buildRequestUrl() {
String uri = requestBuilder.buildRequest(servletContext).getRequestURL().toString();
assertThat(uri, equalTo("http://example.com/test/this/here"));
}
@Test
public void buildRequestSchemeHttp() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getScheme(), equalTo("http"));
}
@Test
public void buildRequestSchemeHttps() throws Exception {
webRequest.setUrl(new URL("https://example.com/"));
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getScheme(), equalTo("https"));
}
@Test
public void buildRequestServerName() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getServerName(), equalTo("example.com"));
}
@Test
public void buildRequestServerPort() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getServerPort(), equalTo(80));
}
@Test
public void buildRequestServerPortDefault() throws Exception {
webRequest.setUrl(new URL("https://example.com/"));
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getServerPort(), equalTo(-1));
}
@Test
public void buildRequestServletContext() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getServletContext(), equalTo(servletContext));
}
@Test
public void buildRequestServletPath() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getServletPath(), equalTo("/this/here"));
}
@Test
public void buildRequestSession() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
HttpSession newSession = actualRequest.getSession();
assertThat(newSession, notNullValue());
assertSingleSessionCookie(
"JSESSIONID=" + newSession.getId() + "; Path=/test; Domain=example.com");
webRequest.setAdditionalHeader("Cookie", "JSESSIONID=" + newSession.getId());
requestBuilder = new HtmlUnitRequestBuilder(sessions, webClient, webRequest);
actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getSession(), sameInstance(newSession));
}
@Test
public void buildRequestSessionWithExistingSession() throws Exception {
String sessionId = "session-id";
webRequest.setAdditionalHeader("Cookie", "JSESSIONID=" + sessionId);
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
HttpSession session = actualRequest.getSession();
assertThat(session.getId(), equalTo(sessionId));
assertSingleSessionCookie("JSESSIONID=" + session.getId() + "; Path=/test; Domain=example.com");
requestBuilder = new HtmlUnitRequestBuilder(sessions, webClient, webRequest);
actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getSession(), equalTo(session));
webRequest.setAdditionalHeader("Cookie", "JSESSIONID=" + sessionId + "NEW");
actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getSession(), not(equalTo(session)));
assertSingleSessionCookie("JSESSIONID=" + actualRequest.getSession().getId()
+ "; Path=/test; Domain=example.com");
}
@Test
public void buildRequestSessionTrue() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
HttpSession session = actualRequest.getSession(true);
assertThat(session, notNullValue());
}
@Test
public void buildRequestSessionFalseIsNull() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
HttpSession session = actualRequest.getSession(false);
assertThat(session, nullValue());
}
@Test
public void buildRequestSessionFalseWithExistingSession() throws Exception {
String sessionId = "session-id";
webRequest.setAdditionalHeader("Cookie", "JSESSIONID=" + sessionId);
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
HttpSession session = actualRequest.getSession(false);
assertThat(session, notNullValue());
}
@Test
public void buildRequestSessionIsNew() throws Exception {
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getSession().isNew(), equalTo(true));
}
@Test
public void buildRequestSessionIsNewFalse() throws Exception {
String sessionId = "session-id";
webRequest.setAdditionalHeader("Cookie", "JSESSIONID=" + sessionId);
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getSession().isNew(), equalTo(false));
}
@Test
public void buildRequestSessionInvalidate() throws Exception {
String sessionId = "session-id";
webRequest.setAdditionalHeader("Cookie", "JSESSIONID=" + sessionId);
MockHttpServletRequest actualRequest = requestBuilder.buildRequest(servletContext);
HttpSession sessionToRemove = actualRequest.getSession();
sessionToRemove.invalidate();
assertThat(sessions.containsKey(sessionToRemove.getId()), equalTo(false));
assertSingleSessionCookie("JSESSIONID=" + sessionToRemove.getId()
+ "; Expires=Thu, 01-Jan-1970 00:00:01 GMT; Path=/test; Domain=example.com");
webRequest.removeAdditionalHeader("Cookie");
requestBuilder = new HtmlUnitRequestBuilder(sessions, webClient, webRequest);
actualRequest = requestBuilder.buildRequest(servletContext);
assertThat(actualRequest.getSession().isNew(), equalTo(true));
assertThat(sessions.containsKey(sessionToRemove.getId()), equalTo(false));
}
// --- setContextPath
@Test
public void setContextPathNull() {
requestBuilder.setContextPath(null);
assertThat(getContextPath(), nullValue());
}
@Test
public void setContextPathEmptyString() {
requestBuilder.setContextPath("");
assertThat(getContextPath(), isEmptyString());
}
@Test(expected = IllegalArgumentException.class)
public void setContextPathDoesNotStartWithSlash() {
requestBuilder.setContextPath("abc/def");
}
@Test(expected = IllegalArgumentException.class)
public void setContextPathEndsWithSlash() {
requestBuilder.setContextPath("/abc/def/");
}
@Test
public void setContextPath() {
String expectedContextPath = "/abc/def";
requestBuilder.setContextPath(expectedContextPath);
assertThat(getContextPath(), equalTo(expectedContextPath));
}
@Test
public void mergeHeader() throws Exception {
String headerName = "PARENT";
String headerValue = "VALUE";
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new HelloController())
.defaultRequest(get("/").header(headerName, headerValue))
.build();
assertThat(mockMvc.perform(requestBuilder).andReturn().getRequest().getHeader(headerName), equalTo(headerValue));
}
@Test
public void mergeSession() throws Exception {
String attrName = "PARENT";
String attrValue = "VALUE";
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new HelloController())
.defaultRequest(get("/").sessionAttr(attrName, attrValue))
.build();
assertThat(mockMvc.perform(requestBuilder).andReturn().getRequest().getSession().getAttribute(attrName), equalTo(attrValue));
}
@Test
public void mergeSessionNotInitialized() throws Exception {
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new HelloController())
.defaultRequest(get("/"))
.build();
assertThat(mockMvc.perform(requestBuilder).andReturn().getRequest().getSession(false), nullValue());
}
@Test
public void mergeParameter() throws Exception {
String paramName = "PARENT";
String paramValue = "VALUE";
String paramValue2 = "VALUE2";
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new HelloController())
.defaultRequest(get("/").param(paramName, paramValue, paramValue2))
.build();
assertThat(Arrays.asList(mockMvc.perform(requestBuilder).andReturn().getRequest().getParameterValues(paramName)), contains(paramValue, paramValue2));
}
@Test
public void mergeCookie() throws Exception {
String cookieName = "PARENT";
String cookieValue = "VALUE";
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new HelloController())
.defaultRequest(get("/").cookie(new Cookie(cookieName,cookieValue)))
.build();
Cookie[] cookies = mockMvc.perform(requestBuilder).andReturn().getRequest().getCookies();
assertThat(cookies, notNullValue());
assertThat(cookies.length, equalTo(1));
Cookie cookie = cookies[0];
assertThat(cookie.getName(), equalTo(cookieName));
assertThat(cookie.getValue(), equalTo(cookieValue));
}
@Test
public void mergeRequestAttribute() throws Exception {
String attrName = "PARENT";
String attrValue = "VALUE";
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new HelloController())
.defaultRequest(get("/").requestAttr(attrName,attrValue))
.build();
assertThat(mockMvc.perform(requestBuilder).andReturn().getRequest().getAttribute(attrName), equalTo(attrValue));
}
private void assertSingleSessionCookie(String expected) {
com.gargoylesoftware.htmlunit.util.Cookie jsessionidCookie = webClient.getCookieManager().getCookie("JSESSIONID");
if (expected == null || expected.contains("Expires=Thu, 01-Jan-1970 00:00:01 GMT")) {
assertThat(jsessionidCookie, nullValue());
return;
}
String actual = jsessionidCookie.getValue();
assertThat("JSESSIONID=" + actual +
"; Path=/test; Domain=example.com", equalTo(expected));
}
private void setParameter(String name, String value) {
webRequest.getRequestParameters().add(new NameValuePair(name, value));
}
private String getContextPath() {
return (String) ReflectionTestUtils.getField(requestBuilder, "contextPath");
}
}

155
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java

@ -0,0 +1,155 @@ @@ -0,0 +1,155 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.io.IOException;
import java.net.URL;
import javax.servlet.http.HttpServletRequest;
import com.gargoylesoftware.htmlunit.WebConnection;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.mockito.Mockito.mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* @author Rob Winch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class MockMvcConnectionBuilderSupportTests {
@Autowired
WebApplicationContext context;
MockMvc mockMvc;
WebConnection delegateConnection;
WebConnection connection;
@Before
public void setup() {
delegateConnection = mock(WebConnection.class);
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
connection = new MockMvcWebConnectionBuilderSupport(mockMvc){}
.createConnection(delegateConnection);
}
@Test(expected = IllegalArgumentException.class)
public void constructorMockMvcNull() {
new MockMvcWebConnectionBuilderSupport((MockMvc)null){};
}
@Test(expected = IllegalArgumentException.class)
public void constructorContextNull() {
new MockMvcWebConnectionBuilderSupport((WebApplicationContext)null){};
}
@Test
public void context() throws Exception {
connection = new MockMvcWebConnectionBuilderSupport(context){}
.createConnection(delegateConnection);
assertMvcProcessed("http://localhost/");
assertDelegateProcessed("http://example.com/");
}
@Test
public void mockMvc() throws Exception {
assertMvcProcessed("http://localhost/");
assertDelegateProcessed("http://example.com/");
}
@Test
public void mockMvcExampleDotCom() throws Exception {
connection = new MockMvcWebConnectionBuilderSupport(context){}
.useMockMvcForHosts("example.com")
.createConnection(delegateConnection);
assertMvcProcessed("http://localhost/");
assertMvcProcessed("http://example.com/");
assertDelegateProcessed("http://other.com/");
}
@Test
public void mockMvcAlwaysUseMockMvc() throws Exception {
connection = new MockMvcWebConnectionBuilderSupport(context){}
.alwaysUseMockMvc()
.createConnection(delegateConnection);
assertMvcProcessed("http://other.com/");
}
@Test
public void defaultContextPathEmpty() throws Exception {
connection = new MockMvcWebConnectionBuilderSupport(context){}
.createConnection(delegateConnection);
assertThat(getWebResponse("http://localhost/abc").getContentAsString(), equalTo(""));;
}
@Test
public void defaultContextPathCustom() throws Exception {
connection = new MockMvcWebConnectionBuilderSupport(context) {
}.contextPath("/abc").createConnection(delegateConnection);
assertThat(getWebResponse("http://localhost/abc/def").getContentAsString(), equalTo("/abc"));;
}
private void assertMvcProcessed(String url) throws Exception {
assertThat(getWebResponse(url), notNullValue());
}
private void assertDelegateProcessed(String url) throws Exception {
assertThat(getWebResponse(url), nullValue());
}
private WebResponse getWebResponse(String url) throws IOException {
return connection.getResponse(new WebRequest(new URL(url)));
}
@Configuration
@EnableWebMvc
static class Config {
@RestController
static class ContextPathController {
@RequestMapping
public String contextPath(HttpServletRequest request) {
return request.getContextPath();
}
}
}
}

117
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java

@ -0,0 +1,117 @@ @@ -0,0 +1,117 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.io.IOException;
import java.net.URL;
import javax.servlet.http.HttpServletRequest;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* @author Rob Winch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class MockMvcWebClientBuilderTests {
@Autowired
WebApplicationContext context;
MockMvc mockMvc;
WebClient webClient;
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
webClient = new WebClient();
}
@Test(expected = IllegalArgumentException.class)
public void mockMvcSetupNull() {
MockMvcWebClientBuilder.mockMvcSetup(null);
}
@Test(expected = IllegalArgumentException.class)
public void webAppContextSetupNull() {
MockMvcWebClientBuilder.webAppContextSetup(null);
}
@Test
public void mockMvcSetupconfigureWebClient() throws Exception {
webClient = MockMvcWebClientBuilder
.mockMvcSetup(mockMvc)
.configureWebClient(webClient);
assertMvcProcessed("http://localhost/test");
assertDelegateProcessed("http://example.com/");
}
@Test
public void mockMvcSetupCreateWebClient() throws Exception {
webClient = MockMvcWebClientBuilder
.mockMvcSetup(mockMvc)
.createWebClient();
assertMvcProcessed("http://localhost/test");
assertDelegateProcessed("http://example.com/");
}
private void assertMvcProcessed(String url) throws Exception {
assertThat(getWebResponse(url).getContentAsString(), equalTo("mvc"));;
}
private void assertDelegateProcessed(String url) throws Exception {
assertThat(getWebResponse(url).getContentAsString(), not(equalTo("mvc")));
}
private WebResponse getWebResponse(String url) throws IOException {
return webClient.getWebConnection().getResponse(new WebRequest(new URL(url)));
}
@Configuration
@EnableWebMvc
static class Config {
@RestController
static class ContextPathController {
@RequestMapping
public String contextPath(HttpServletRequest request) {
return "mvc";
}
}
}
}

92
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java

@ -0,0 +1,92 @@ @@ -0,0 +1,92 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.io.IOException;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
/**
* @author Rob Winch
*/
public class MockMvcWebConnectionTests {
MockMvc mockMvc;
WebClient webClient;
@Before
public void setup() {
mockMvc = MockMvcBuilders
.standaloneSetup(new HelloController(), new ForwardController())
.build();
webClient = new WebClient();
}
@Test
public void contextPathNull() throws IOException {
webClient.setWebConnection(new MockMvcWebConnection(mockMvc, null));
Page page = webClient.getPage("http://localhost/context/a");
assertThat(page.getWebResponse().getStatusCode(), equalTo(200));;
}
@Test
public void contextPathExplicit() throws IOException {
webClient.setWebConnection(new MockMvcWebConnection(mockMvc, "/context"));
Page page = webClient.getPage("http://localhost/context/a");
assertThat(page.getWebResponse().getStatusCode(), equalTo(200));;
}
@Test
public void contextPathEmpty() throws IOException {
webClient.setWebConnection(new MockMvcWebConnection(mockMvc, ""));
Page page = webClient.getPage("http://localhost/context/a");
assertThat(page.getWebResponse().getStatusCode(), equalTo(200));;
}
@Test
public void forward() throws IOException {
webClient.setWebConnection(new MockMvcWebConnection(mockMvc, ""));
Page page = webClient.getPage("http://localhost/forward");
assertThat(page.getWebResponse().getContentAsString(), equalTo("hello"));;
}
@Test(expected = IllegalArgumentException.class)
public void contextPathDoesNotStartWithSlash() throws IOException {
new MockMvcWebConnection(mockMvc, "context");
}
@Test(expected = IllegalArgumentException.class)
public void contextPathEndsWithSlash() throws IOException {
new MockMvcWebConnection(mockMvc, "/context/");
}
}

135
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java

@ -0,0 +1,135 @@ @@ -0,0 +1,135 @@
/*
* Copyright 2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.net.URL;
import java.util.List;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletResponse;
/**
*
* @author Rob Winch
*/
public class MockWebResponseBuilderTests {
private WebRequest webRequest;
private MockHttpServletResponse httpServletResponse;
private MockWebResponseBuilder responseBuilder;
@Before
public void setUp() throws Exception {
webRequest = new WebRequest(new URL("http://example.com:80/test/this/here"));
httpServletResponse = new MockHttpServletResponse();
responseBuilder = new MockWebResponseBuilder(System.currentTimeMillis(), webRequest, httpServletResponse);
}
// --- constructor
@Test(expected = IllegalArgumentException.class)
public void constructorNullWebRequest() {
new MockWebResponseBuilder(0L, null, httpServletResponse);
}
@Test(expected = IllegalArgumentException.class)
public void constructorNullResponse() throws Exception {
new MockWebResponseBuilder(0L, new WebRequest(new URL("http://example.com:80/test/this/here")), null);
}
// --- build
@Test
public void buildContent() throws Exception {
httpServletResponse.getWriter().write("expected content");
WebResponse webResponse = responseBuilder.build();
assertThat(webResponse.getContentAsString(), equalTo("expected content"));;
}
@Test
public void buildContentCharset() throws Exception {
httpServletResponse.addHeader("Content-Type", "text/html; charset=UTF-8");
WebResponse webResponse = responseBuilder.build();
assertThat(webResponse.getContentCharset(), equalTo("UTF-8"));;
}
@Test
public void buildContentType() throws Exception {
httpServletResponse.addHeader("Content-Type", "text/html; charset-UTF-8");
WebResponse webResponse = responseBuilder.build();
assertThat(webResponse.getContentType(), equalTo("text/html"));;
}
@Test
public void buildResponseHeaders() throws Exception {
httpServletResponse.addHeader("Content-Type", "text/html");
httpServletResponse.addHeader("X-Test", "value");
WebResponse webResponse = responseBuilder.build();
List<NameValuePair> responseHeaders = webResponse.getResponseHeaders();
assertThat(responseHeaders.size(), equalTo(2));;
NameValuePair header = responseHeaders.get(0);
assertThat(header.getName(), equalTo("Content-Type"));;
assertThat(header.getValue(), equalTo("text/html"));;
header = responseHeaders.get(1);
assertThat(header.getName(), equalTo("X-Test"));;
assertThat(header.getValue(), equalTo("value"));;
}
@Test
public void buildStatus() throws Exception {
WebResponse webResponse = responseBuilder.build();
assertThat(webResponse.getStatusCode(), equalTo(200));;
assertThat(webResponse.getStatusMessage(), equalTo("OK"));;
}
@Test
public void buildStatusNotOk() throws Exception {
httpServletResponse.setStatus(401);
WebResponse webResponse = responseBuilder.build();
assertThat(webResponse.getStatusCode(), equalTo(401));;
assertThat(webResponse.getStatusMessage(), equalTo("Unauthorized"));;
}
@Test
public void buildStatusCustomMessage() throws Exception {
httpServletResponse.sendError(401, "Custom");
WebResponse webResponse = responseBuilder.build();
assertThat(webResponse.getStatusCode(), equalTo(401));;
assertThat(webResponse.getStatusMessage(), equalTo("Custom"));;
}
@Test
public void buildWebRequest() throws Exception {
WebResponse webResponse = responseBuilder.build();
assertThat(webResponse.getWebRequest(), equalTo(webRequest));;
}
}

43
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcherTests.java

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit;
import java.net.URL;
import com.gargoylesoftware.htmlunit.WebRequest;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import org.junit.Test;
import org.springframework.test.web.servlet.htmlunit.UrlRegexRequestMatcher;
import org.springframework.test.web.servlet.htmlunit.WebRequestMatcher;
/**
* @author Rob Winch
*/
public class UrlRegexRequestMatcherTests {
@Test
public void classlevelJavadoc() throws Exception {
WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*");
boolean matches = cdnMatcher.matches(new WebRequest(new URL("http://code.jquery.com/jquery-1.11.0.min.js")));
assertThat(matches, equalTo(true));
matches = cdnMatcher.matches(new WebRequest(new URL("http://localhost/jquery-1.11.0.min.js")));
assertThat(matches, equalTo(false));
}
}

136
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java

@ -0,0 +1,136 @@ @@ -0,0 +1,136 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.springframework.test.web.servlet.htmlunit.webdriver;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.MatcherAssert.assertThat;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
/**
* @author Rob Winch
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class MockMvcHtmlUnitDriverBuilderTests {
public static final String EXPECTED_BODY = "MockMvcHtmlUnitDriverBuilderTests mvc";
@Autowired
WebApplicationContext context;
MockMvc mockMvc;
HtmlUnitDriver driver;
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
@Test(expected = IllegalArgumentException.class)
public void mockMvcSetupNull() {
MockMvcHtmlUnitDriverBuilder.mockMvcSetup(null);
}
@Test(expected = IllegalArgumentException.class)
public void webAppContextSetupNull() {
MockMvcHtmlUnitDriverBuilder.webAppContextSetup(null);
}
@Test
public void mockMvcSetupConfigureDriver() throws Exception {
driver = MockMvcHtmlUnitDriverBuilder
.mockMvcSetup(mockMvc)
.configureDriver(new WebConnectionHtmlUnitDriver());
assertMvcProcessed("http://localhost/test");
assertDelegateProcessed("http://example.com/");
}
@Test
public void mockMvcSetupCreateDriver() throws Exception {
driver = MockMvcHtmlUnitDriverBuilder
.mockMvcSetup(mockMvc)
.createDriver();
assertMvcProcessed("http://localhost/test");
assertDelegateProcessed("http://example.com/");
}
@Test
public void javascriptEnabledDefaultEnabled() {
driver = MockMvcHtmlUnitDriverBuilder
.mockMvcSetup(mockMvc)
.createDriver();
assertThat(driver.isJavascriptEnabled(), equalTo(true));
}
@Test
public void javascriptEnabledDisabled() {
driver = MockMvcHtmlUnitDriverBuilder
.mockMvcSetup(mockMvc)
.javascriptEnabled(false)
.createDriver();
assertThat(driver.isJavascriptEnabled(), equalTo(false));
}
private void assertMvcProcessed(String url) throws Exception {
assertThat(get(url), containsString(EXPECTED_BODY));
}
private void assertDelegateProcessed(String url) throws Exception {
assertThat(get(url), not(containsString(EXPECTED_BODY)));
}
private String get(String url) throws IOException {
driver.get(url);
return driver.getPageSource();
}
@Configuration
@EnableWebMvc
static class Config {
@RestController
static class ContextPathController {
@RequestMapping
public String contextPath(HttpServletRequest request) {
return EXPECTED_BODY;
}
}
}
}

55
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriverTests.java

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
package org.springframework.test.web.servlet.htmlunit.webdriver;
import com.gargoylesoftware.htmlunit.WebConnection;
import com.gargoylesoftware.htmlunit.WebRequest;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.fail;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.mockito.Matchers.any;
import org.mockito.Mock;
import static org.mockito.Mockito.when;
import org.mockito.runners.MockitoJUnitRunner;
/**
* @author Rob Winch
*/
// tag::junit-spring-setup[]
@RunWith(MockitoJUnitRunner.class)
public class WebConnectionHtmlUnitDriverTests {
@Mock
WebConnection connection;
WebConnectionHtmlUnitDriver driver;
@Before
public void setup() throws Exception {
driver = new WebConnectionHtmlUnitDriver();
when(connection.getResponse(any(WebRequest.class))).thenThrow(new InternalError(""));
}
@Test
public void getWebConnectionDefaultNotNull() {
assertThat(driver.getWebConnection(), notNullValue());
}
@Test
public void setWebConnection() {
driver.setWebConnection(connection);
assertThat(driver.getWebConnection(), equalTo(connection));
try {
driver.get("https://example.com");
fail("Expected Exception");
} catch (InternalError success) {}
}
@Test(expected = IllegalArgumentException.class)
public void setWebConnectionNull() {
driver.setWebConnection(null);
}
}

772
src/asciidoc/testing.adoc

@ -4154,6 +4154,778 @@ https://github.com/spring-projects/spring-mvc-showcase[spring-mvc-showcase] has @@ -4154,6 +4154,778 @@ https://github.com/spring-projects/spring-mvc-showcase[spring-mvc-showcase] has
coverage based on Spring MVC Test.
[[spring-mvc-test-server-htmlunit]]
==== HtmlUnit Integration
Spring provides integration between <<spring-mvc-test-server,MockMvc>> and
http://htmlunit.sourceforge.net/[HtmlUnit]. This simplifies performing end to end testing
when using HTML based views. This integration enables developers to:
* Easily test pages using tools (i.e. http://htmlunit.sourceforge.net/[HtmlUnit],
http://seleniumhq.org/projects/webdriver/[WebDriver], &
http://www.gebish.org/manual/current/testing.html#spock_junit__testng[Geb]) that we
already use for integration testing without starting an application server
* Support testing of JavaScript
* Optionally test using mock services to speed up testing.
* Share logic between end-to-end tests and integration tests
[NOTE]
====
MockMvc will work with templating technologies that do not rely on a Servlet Container
(i.e. Thymeleaf, Freemarker, Velocity, etc). It does not work with JSPs since they rely on
the Servlet Container.
====
[[spring-mvc-test-server-htmlunit-why]]
===== Why HtmlUnit Integration?
The most obvious question that comes to mind is "Why do I need this?" The answer is best
found by exploring a very basic sample application. Assume you have a Spring MVC web
application that allows CRUD operations on a `Message` object. The application also allows
paging through all messages. How would you go about testing it?
With Spring MVC Test, we can easily test if we are able to create a `Message`.
[source,java]
----
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param("summary", "Spring Rocks")
.param("text", "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
----
What if we want to test our form view that allows us to create the message? For example,
assume our form looks like the following snippet:
[source,xml]
----
<form id="messageForm" action="/messages/" method="post">
<div class="pull-right"><a href="/messages/">Messages</a></div>
<label for="summary">Summary</label>
<input type="text" class="required" id="summary" name="summary" value="" />
<label for="text">Message</label>
<textarea id="text" name="text"></textarea>
<div class="form-actions">
<input type="submit" value="Create" />
</div>
</form>
----
How do we ensure that our form will produce the correct request to create a new message? A
naive attempt would look like this:
[source,java]
----
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='summary']").exists())
.andExpect(xpath("//textarea[@name='text']").exists());
----
This test has some obvious problems. If we updated our controller to use the parameter
"message" instead of "text", our test would would incorrectly pass. To resolve this we
could combine our two tests:
[[spring-mvc-test-server-htmlunit-mock-mvc-test]]
[source,java]
----
String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
.andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param(summaryParamName, "Spring Rocks")
.param(textParamName, "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
----
This would reduce the risk of our test incorrectly passing, but there are still some
problems:
* What if we had multiple forms on our page? Admittedly we could update our xpath
expressions, but they get more complicated the more factors we take into account (are the
fields the correct type, are the fields enabled, etc).
* Another issue is that we are doing double the work we would expect.
We must first verify the view and then we submit the view with the same parameters we just
verified.
Ideally this could be done all at once.
* Last, there are some things that we still cannot account for. For example, what if the
form has JavaScript validation that we wish to validate too?
The overall problem is that testing a web page is not a single interaction. Instead, it is
a combination of how the user interacts with a web page and how that web page interacts
with other resources. For example, the result of form view is used as an input to a user
for creating a message. Another example is that our form view utilizes additional
resources, like JavaScript validation, that impact the behavior of the page.
[[spring-mvc-test-server-htmlunit-why-integration]]
====== Integration testing to the rescue?
To resolve the issues above we could perform integration testing, but this has some
obvious drawbacks. Consider testing the view that allows us to page through the messages.
We might need the following tests:
* Does our page display a message to the user indicating that no results are available
when the messages are empty?
* Does our page properly display a single message?
* Does our page properly support paging?
To set these tests up we would need to ensure our database contained the proper messages
in it. This leads to a number of problems:
* Ensuring the proper messages are in the database can be tedious (think possible foreign
keys).
* Testing would be slow since each test would require ensuring the database was in the
correct state.
* Since our database needs to be in a specific state, we cannot run the test in parallel.
* Assertions on things like auto generated ids, timestamps, etc can be challenging.
These problems do not mean that we should abandon integration testing all together.
Instead, we can reduce the number of integration tests by moving our detailed tests to use
mock services which will perform much faster. We can then use fewer integration tests that
validate simple workflows to ensure that everything works together properly.
[[spring-mvc-test-server-htmlunit-why-mockmvc]]
====== Enter HtmlUnit Integration
So how can we provide a balance between testing the interactions of our pages and still
get performance? I'm sure you already guessed it...integrating with HtmlUnit
will allow us to:
* Easily test our pages using tools (i.e. HtmlUnit, WebDriver, & Geb) that we already use
for integration testing without starting an application server
* Support testing of JavaScript
* Optionally test using mock services to speed up testing.
* Share logic between end-to-end tests and integration tests
[[spring-mvc-test-server-htmlunit-options]]
====== HtmlUnit Integration Options
There are a number of ways to integrate with HtmlUnit. You can find a summary below:
* <<spring-mvc-test-server-htmlunit-mah,MockMvc and HtmlUnit>> - Use this option if you want the raw libraries
* <<spring-mvc-test-server-htmlunit-webdriver,MockMvc and WebDriver>> - Use this option to ease development and be able to reuse code
between integration and end-to-end testing.
* <<spring-mvc-test-server-htmlunit-geb,MockMvc and Geb>> - Use this option if you like using Groovy for testing, would like to
ease development, and be able to reuse code between integration and end-to-end testing.
[[spring-mvc-test-server-htmlunit-mah]]
===== MockMvc and HtmlUnit
This section describes how to integrate `MockMvc` and HtmlUnit. Use this option if you
want to use the raw HtmlUnit libraries.
[[spring-mvc-test-server-htmlunit-mah-setup]]
====== MockMvc and HtmlUnit Setup
We can easily create an HtmlUnit `WebClient` that integrates with `MockMvc` using the
following:
[source,java]
----
@Autowired
WebApplicationContext context;
WebClient webClient;
@Before
public void setup() {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.createWebClient();
}
----
[NOTE]
====
This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage see
<<Advanced MockMvcWebClientBuilder>>
====
This will ensure any URL that has a host of "localhost" will be directed at our MockMvc
instance without the need for HTTP. Any other URL will be requested as normal. This allows
for easily testing with the use of CDNs.
[[spring-mvc-test-server-htmlunit-mah-usage]]
====== MockMvc and HtmlUnit Usage
Now we can use HtmlUnit as we normally would, but without the need to deploy our
application. For example, we can request the view to create a message with the following:
[source,java]
----
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
----
[NOTE]
====
The the context path is "". Alternatively, we could have specified the context path as
illustrated in <<Advanced MockMvcWebClientBuilder>>.
====
We can then fill out the form and submit it to create a message.
[source,java]
----
HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
textInput.setText("In case you didn't know, Spring Rocks!");
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
HtmlPage newMessagePage = submit.click();
----
Finally, we can verify that a new message was created successfully
[source,java]
----
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
String id = newMessagePage.getHtmlElementById("id").getTextContent();
assertThat(id).isEqualTo("123");
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
assertThat(summary).isEqualTo("Spring Rocks");
String text = newMessagePage.getHtmlElementById("text").getTextContent();
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
----
This improves on our <<spring-mvc-test-server-htmlunit-mock-mvc-test,MockMvc test>> in a
number of ways. First we no longer have to explicitly verify our form and then create a
request that looks like the form. Instead, we request the form, fill it out, and submit
it. This reduces the overhead significantly.
Another important factor is that
http://htmlunit.sourceforge.net/javascript.html[HtmlUnit uses Mozilla Rhino engine] to
evaluate JavaScript on your pages. This means, that we can verify our JavaScript methods
as well!
Refer to the http://htmlunit.sourceforge.net/gettingStarted.html[HtmlUnit documentation]
for additional information about using HtmlUnit.
[[spring-mvc-test-server-htmlunit-mah-advanced-builder]]
====== Advanced MockMvcWebClientBuilder
In our example above we used `MockMvcWebClientBuilder` in the simplest way possible.
[source,java]
----
@Autowired
WebApplicationContext context;
WebClient webClient;
@Before
public void setup() {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.createWebClient();
}
----
We could also specify some optional arguments:
[source,java]
----
@Before
public void setup() {
webClient = MockMvcWebClientBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only
// the following will use MockMvc for example.com and example.org too
.useMockMvcForHosts("example.com","example.org")
.createWebClient();
}
----
We could also perform the exact same setup using the following:
[source,java]
----
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.build();
webClient = MockMvcWebClientBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only
// the following will use MockMvc for example.com and example.org too
.useMockMvcForHosts("example.com","example.org")
.createWebClient();
----
This is more verbose, but by building the `WebClient` with a `MockMvc` instance we have
the full power of `MockMvc` at our finger tips. Ultimately, this is simply performing the
following:
[TIP]
====
For additional information on creating a `MockMvc` instance refer to
<<spring-mvc-test-server-setup-options>>.
====
[[spring-mvc-test-server-htmlunit-webdriver]]
===== MockMvc and WebDriver
In the previous section, we have already seen how to use MockMvc with HtmlUnit.
In this section, we will leverage additional abstractions within
http://docs.seleniumhq.org/projects/webdriver/[WebDriver] to make things even easier.
[[spring-mvc-test-server-htmlunit-webdriver-why]]
====== Why WebDriver and MockMvc?
We can already use HtmlUnit and MockMvc, so why would we want to use WebDriver? WebDriver
provides a very elegant API and allows us to easily organize our code. To better
understand, let's explore an example.
[NOTE]
====
Despite being a part of http://docs.seleniumhq.org/[Selenium], WebDriver does not require
a Selenium Server to run your tests.
====
Suppose we need to ensure that a message is created properly. The tests involve finding
the html inputs, filling them out, and making various assertions.
There are many tests because we want to test error conditions as well. For example, we
want to ensure that if we fill out only part of the form we get an error. If we fill out
the entire form, the newly created message is displayed afterwards.
If one of the fields was named "summary", then we might have something like the following
repeated everywhere within our tests:
[source,java]
----
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
----
So what happens if we change the id to be "smmry".
This means we would have to update all of our tests! Instead we would hope that we wrote a
bit more elegant code where filling out the form was in its own method:
[source,java]
----
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
setSummary(currentPage, summary);
...
}
public void setSummary(HtmlPage currentPage, String summary) {
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
}
----
This ensures that if we change the UI we do not have to update all of our tests.
We might take it a step further and place this logic within an Object that represents the
`HtmlPage` we are currently on.
[source,java]
----
public class CreateMessagePage {
HtmlPage currentPage;
HtmlTextInput summaryInput;
HtmlSubmitInput submit;
public CreateMessagePage(HtmlPage currentPage) {
this.currentPage = currentPage;
this.summaryInput = currentPage.getHtmlElementById("summary");
this.submit = currentPage.getHtmlElementById("submit");
}
public <T> T createMessage(String summary, String text) throws Exception {
setSummary(summary);
HtmlPage result = submit.click();
boolean error = CreateMessagePage.at(result);
return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
}
public void setSummary(String summary) throws Exception {
summaryInput.setValueAttribute(summary);
}
public static boolean at(HtmlPage page) {
return "Create Message".equals(page.getTitleText());
}
}
----
Formerly, this pattern is known as the
https://code.google.com/p/selenium/wiki/PageObjects[Page Object Pattern]. While we can
certainly do this with HtmlUnit, WebDriver provides some tools that we will explore in the
following sections make this pattern much easier.
[[spring-mvc-test-server-htmlunit-webdriver-setup]]
====== MockMvc and WebDriver Setup
We can easily create a WebDriver implementation that integrates with MockMvc using the
following:
[source,java]
----
@Autowired
WebApplicationContext context;
WebDriver driver;
@Before
public void setup() {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.createDriver();
}
----
[NOTE]
====
This is a simple example of using `MockMvcHtmlUnitDriverBuilder`.
For more advanced usage, refer to <<Advanced MockMvcHtmlUnitDriverBuilder>>
====
This will ensure any URL that has a host of "localhost" will be directed at our MockMvc
instance without the need for HTTP. Any other URL will be requested as normal. This allows
for easily testing with the use of CDNs.
[[spring-mvc-test-server-htmlunit-webdriver-usage]]
====== MockMvc and WebDriver Usage
Now we can use WebDriver as we normally would, but without the need to deploy our
application. For example, we can request the view to create a message with the following:
[source,java]
----
CreateMessagePage page = CreateMessagePage.to(driver);
----
We can then fill out the form and submit it to create a message.
[source,java]
----
ViewMessagePage viewMessagePage =
page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
----
This improves on the design of our
<<spring-mvc-test-server-htmlunit-mah-usage,HtmlUnit test>> by leveraging the Page Object
Pattern. As we mentioned in <<spring-mvc-test-server-htmlunit-webdriver-why>>, we could
use the Page Object Pattern with HtmlUnit, but it is much easier now. Let's take a look at
our `CreateMessagePage`.
[source,java]
----
public class CreateMessagePage
extends AbstractPage { // <1>
// <2>
private WebElement summary;
private WebElement text;
// <3>
@FindBy(css = "input[type=submit]")
private WebElement submit;
public CreateMessagePage(WebDriver driver) {
super(driver);
}
public <T> T createMessage(Class<T> resultPage, String summary, String details) {
this.summary.sendKeys(summary);
this.text.sendKeys(details);
this.submit.click();
return PageFactory.initElements(driver, resultPage);
}
public static CreateMessagePage to(WebDriver driver) {
driver.get("http://localhost:9990/mail/messages/form");
return PageFactory.initElements(driver, CreateMessagePage.class);
}
}
----
<1> The first thing you will notice is that our `CreateMessagePage` extends the
`AbstractPage`. We won't go over the details of `AbstractPage`, but in summary it contains
all the common functionality of all our pages. For example, if your application has a
navigational bar, global error messages, etc. This logic can be placed in a shared
location.
<2> The next thing you will find is that we have a member variable for each of the parts
of the HTML, `WebElement`, we are interested in. ``WebDriver``'s
https://code.google.com/p/selenium/wiki/PageFactory[PageFactory] allows us to remove a lot
of code from HtmlUnit version of `CreateMessagePage` by automatically resolving each
`WebElement`. The
http://selenium.googlecode.com/git/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[PageFactory#initElements(WebDriver,Class<T>)]
method will automatically resolve each `WebElement` by using the field name and trying to
look it up by id or name of the element on the HTML page.
<3> We can use the
https://code.google.com/p/selenium/wiki/PageFactory#Making_the_Example_Work_Using_Annotations[@FindBy annotation]
to override the default. Our example demonstrates how we can use the `@FindBy` annotation
to lookup our submit button using the css selector of *input[type=submit]*.
Finally, we can verify that a new message was created successfully
[source,java]
----
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
----
We can see that our `ViewMessagePage` can allow us to interact with our custom domain
model. For example, it exposes a method that returns a `Message` object.
[source,java]
----
public Message getMessage() throws ParseException {
Message message = new Message();
message.setId(getId());
message.setCreated(getCreated());
message.setSummary(getSummary());
message.setText(getText());
return message;
}
----
We can then leverage the rich domain objects in our assertions.
Last, don't forget to close the `WebDriver` instance when we are done.
[source,java]
----
@After
public void destroy() {
if(driver != null) {
driver.close();
}
}
----
For additional information on using WebDriver, refer to the
https://code.google.com/p/selenium/wiki/GettingStarted[WebDriver documentation].
[[spring-mvc-test-server-htmlunit-webdriver-advanced-builder]]
====== Advanced MockMvcHtmlUnitDriverBuilder
In our example above we used `MockMvcHtmlUnitDriverBuilder` in the simplest way possible.
[source,java]
----
WebClient webClient;
@Before
public void setup() {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.createWebClient();
}
----
We could also specify some optional arguments:
[source,java]
----
WebClient webClient;
@Before
public void setup() {
webClient = MockMvcWebClientBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only
// the following will use MockMvc for example.com and example.org too
.useMockMvcForHosts("example.com","example.org")
.createWebClient();
}
----
We could also perform the exact same setup using the following:
[source,java]
----
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
webClient = MockMvcWebClientBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only
// the following will use MockMvc for example.com and example.org too
.useMockMvcForHosts("example.com","example.org")
.createWebClient();
----
This is more verbose, but by building the `WebDriver` with a `MockMvc` instance we have
the full power of `MockMvc` at our finger tips. Ultimately, this is simply performing the
following:
[TIP]
====
For additional information on creating a `MockMvc` instance refer to
<<spring-mvc-test-server-setup-options>>.
====
[[spring-mvc-test-server-htmlunit-geb]]
===== MockMvc and Geb
In the previous section, we saw how to use MockMvc with WebDriver.
In this section, we will use http://www.gebish.org/[Geb] to make our tests more Groovy.
[[spring-mvc-test-server-htmlunit-geb-why]]
====== Why Geb and MockMvc?
Geb is backed by WebDriver, so it offers many of the
<<spring-mvc-test-server-htmlunit-webdriver-why,same benefits>> we got from WebDriver.
However, Geb makes things even easier by taking care of some of the boiler plate code for
us.
[[spring-mvc-test-server-htmlunit-geb-setup]]
====== MockMvc and Geb Setup
We can easily initialize Geb with a WebDriver implementation that uses `MockMvc` with the
following:
[source,groovy]
----
def setup() {
browser.driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context, springSecurity())
.createDriver()
}
----
[NOTE]
====
This is a simple example of using `MockMvcHtmlUnitDriverBuilder`.
For more advanced usage, refer to <<Advanced MockMvcHtmlUnitDriverBuilder>>
====
This will ensure any URL that has a host of "localhost" will be directed at our MockMvc
instance without the need for HTTP. Any other URL will be requested as normal. This allows
for easily testing with the use of CDNs.
[[spring-mvc-test-server-htmlunit-geb-usage]]
====== MockMvc and Geb Usage
Now we can use Geb as we normally would, but without the need to deploy our application.
For example, we can request the view to create a message with the following:
[source,groovy]
----
to CreateMessagePage
----
We can then fill out the form and submit it to create a message.
[source,groovy]
----
when:
form.summary = expectedSummary
form.text = expectedMessage
submit.click(ViewMessagePage)
----
Any unrecognized method calls or property accesses/references that are not found will be
forwarded to the current page object. This removes a lot of the boilerplate code we needed
when using WebDriver directly.
Additionally, this improves on the design of our
<<spring-mvc-test-server-htmlunit-mah-usage,HtmlUnit test>>. The most obvious change is
that we are now using the Page Object Pattern. As we mentioned in
<<spring-mvc-test-server-htmlunit-webdriver-why>>, we could use the Page Object Pattern
with HtmlUnit, but it is much easier now.
Let's take a look at our `CreateMessagePage`.
[source,groovy]
----
class CreateMessagePage extends Page {
static at = { assert title == 'Messages : Create'; true }
static url = 'messages/form'
static content = {
submit { $('input[type=submit]') }
form { $('form') }
errors(required:false) { $('label.error, .alert-error')?.text() }
}
}
----
The first thing you will notice is that our `CreateMessagePage` extends the `Page`.
We won't go over the details of `Page`, but in summary it contains base functionality for all our pages.
The next thing you will notice is that we define a URL in which this page can be found.
This allows us to navigate to the page with:
[source,groovy]
----
to CreateMessagePage
----
We also have a closure that determines if we are at the specified page.
It should return true if we are on the correct page.
This is why we can assert that we are on the correct page with:
[source,groovy]
----
then:
at CreateMessagePage
errors.contains('This field is required.')
----
[NOTE]
====
We use an assertion in the closure, so we can determine where things went wrong if we were
at the wrong page.
====
We last create a content closure that specifies all the areas of interest within the page.
We can use a
http://www.gebish.org/manual/current/intro.html#the_jquery_ish_navigator_api[jQuery-ish Navigator API]
to select the content we are interested in.
Finally, we can verify that a new message was created successfully
[source,groovy]
----
then:
at ViewMessagePage
success == 'Successfully created a new message'
id
date
summary == expectedSummary
message == expectedMessage
----
[[spring-mvc-test-client]]
==== Client-Side REST Tests
Client-side tests are for code using the `RestTemplate`. The goal is to define expected

Loading…
Cancel
Save