Browse Source

Merge pull request #825 from rwinch/SPR-13158

* SPR-13158:
  Polish MockMvc HtmlUnit Support
  Ensure spring-test builds against HttpComponents 4.5
  Upgrade to HtmlUnit 2.18 GA
  Mention HtmlUnit support in the What's New section
  Introduce support for HtmlUnit in Spring MVC Test
pull/849/head
Sam Brannen 11 years ago
parent
commit
98c7a63bb7
  1. 5
      build.gradle
  2. 107
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java
  3. 44
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/ForwardRequestPostProcessor.java
  4. 89
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcher.java
  5. 516
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java
  6. 108
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java
  7. 152
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java
  8. 165
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java
  9. 104
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java
  10. 55
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcher.java
  11. 36
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/WebRequestMatcher.java
  12. 6
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/package-info.java
  13. 123
      spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java
  14. 106
      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. 143
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java
  17. 34
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/ForwardController.java
  18. 38
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HelloController.java
  19. 81
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcherTests.java
  20. 785
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java
  21. 162
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java
  22. 123
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java
  23. 97
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java
  24. 138
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java
  25. 45
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcherTests.java
  26. 138
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java
  27. 76
      spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriverTests.java
  28. 772
      src/asciidoc/testing.adoc
  29. 4
      src/asciidoc/whats-new.adoc

5
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.18"
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}")
@ -1028,6 +1032,7 @@ project("spring-test") { @@ -1028,6 +1032,7 @@ project("spring-test") {
}
testCompile("org.hsqldb:hsqldb:${hsqldbVersion}")
testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}")
testCompile("org.apache.httpcomponents:httpclient:$httpclientVersion")
}
task testNG(type: Test) {

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

@ -0,0 +1,107 @@ @@ -0,0 +1,107 @@
/*
* 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 org.springframework.util.Assert;
import com.gargoylesoftware.htmlunit.WebConnection;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
/**
* Implementation of {@link WebConnection} that allows delegating to various
* {@code WebConnection} implementations.
*
* <p>For example, if you host your JavaScript on the domain {@code code.jquery.com},
* you might want to use the following.
*
* <pre class="code">
* 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
* @author Sam Brannen
* @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 must not be null");
Assert.notEmpty(connections, "connections must not 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 : this.connections) {
if (connection.getMatcher().matches(request)) {
return connection.getDelegate().getResponse(request);
}
}
return this.defaultConnection.getResponse(request);
}
public static final 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;
}
}
}

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

@ -0,0 +1,44 @@ @@ -0,0 +1,44 @@
/*
* 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
* @author Sam Brannen
* @since 4.2
*/
final class ForwardRequestPostProcessor implements RequestPostProcessor {
private final String forwardUrl;
public ForwardRequestPostProcessor(String forwardUrl) {
Assert.hasText(forwardUrl, "forwardUrl must not be null or empty");
this.forwardUrl = forwardUrl;
}
@Override
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
request.setServletPath(this.forwardUrl);
return request;
}
}

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

@ -0,0 +1,89 @@ @@ -0,0 +1,89 @@
/*
* 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;
/**
* A {@link WebRequestMatcher} that allows matching on the host and optionally
* the port of {@code WebRequest#getUrl()}.
*
* <p>For example, the following would match any request to the host
* {@code "code.jquery.com"} without regard for the port.
*
* <pre class="code">WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com");</pre>
*
* <p>Multiple hosts can also be passed in. For example, the following would
* match any request to the host {@code "code.jquery.com"} or the host
* {@code "cdn.com"} without regard for the port.
*
* <pre class="code">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 "code.jquery.com"} with the port of {@code 80}.
*
* <pre class="code">WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com:80");</pre>
*
* <p>The above {@code cdnMatcher} would match {@code "http://code.jquery.com/jquery.js"}
* which has a default port of {@code 80} and {@code "http://code.jquery.com:80/jquery.js"}.
* However, it would not match {@code "https://code.jquery.com/jquery.js"}
* which has a default port of {@code 443}.
*
* @author Rob Winch
* @author Sam Brannen
* @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>();
/**
* Create a new {@code HostRequestMatcher} for the given hosts &mdash;
* for example: {@code "localhost"}, {@code "example.com:443"}, etc.
* @param hosts the hosts to match on
*/
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 (this.hosts.contains(host)) {
return true;
}
int port = url.getPort();
if (port == -1) {
port = url.getDefaultPort();
}
String hostAndPort = host + ":" + port;
return this.hosts.contains(hostAndPort);
}
}

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

@ -0,0 +1,516 @@ @@ -0,0 +1,516 @@
/*
* 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 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;
import com.gargoylesoftware.htmlunit.CookieManager;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
/**
* Internal class used to transform a {@link WebRequest} into a
* {@link MockHttpServletRequest} using Spring MVC Test's {@link RequestBuilder}.
*
* <p>By default the first path segment of the URL is used as the contextPath.
* To override this default see {@link #setContextPath(String)}.
*
* @author Rob Winch
* @author Sam Brannen
* @since 4.2
* @see MockMvcWebConnection
*/
final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable {
private static final Pattern LOCALE_PATTERN = Pattern.compile("^\\s*(\\w{2})(?:-(\\w{2}))?(?:;q=(\\d+\\.\\d+))?$");
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;
/**
* Construct a new {@code HtmlUnitRequestBuilder}.
* @param sessions a {@link Map} from session {@linkplain HttpSession#getId() IDs}
* to currently managed {@link HttpSession} objects; never {@code null}
* @param webClient the WebClient for retrieving cookies
* @param webRequest the {@link WebRequest} to transform into a
* {@link MockHttpServletRequest}; never {@code null}
*/
public HtmlUnitRequestBuilder(Map<String, MockHttpSession> sessions, WebClient webClient, WebRequest webRequest) {
Assert.notNull(sessions, "sessions map must not be null");
Assert.notNull(webClient, "webClient must not be null");
Assert.notNull(webRequest, "webRequest must not be null");
this.sessions = sessions;
this.webClient = webClient;
this.webRequest = webRequest;
}
public MockHttpServletRequest buildRequest(ServletContext servletContext) {
String charset = getCharset();
String httpMethod = this.webRequest.getHttpMethod().name();
UriComponents uriComponents = uriComponents();
MockHttpServletRequest result = new HtmlUnitMockHttpServletRequest(servletContext, httpMethod,
uriComponents.getPath());
parent(result, this.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 (this.parentPostProcessor != null) {
request = this.parentPostProcessor.postProcessRequest(request);
}
if (this.forwardPostProcessor != null) {
request = this.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));
}
}
/**
* Set the contextPath to be used.
* <p>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
* an empty string, or it must start with a "/" and not end with a "/".
* @param contextPath a valid contextPath
* @throws IllegalArgumentException if the contextPath is not a valid {@link HttpServletRequest#getContextPath()}
*/
public void setContextPath(String contextPath) {
MockMvcWebConnection.validateContextPath(contextPath);
this.contextPath = contextPath;
}
public void setForwardPostProcessor(RequestPostProcessor forwardPostProcessor) {
this.forwardPostProcessor = forwardPostProcessor;
}
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 = this.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 (this.contextPath == null) {
List<String> pathSegments = uriComponents.getPathSegments();
if (pathSegments.isEmpty()) {
result.setContextPath("");
}
else {
result.setContextPath("/" + pathSegments.get(0));
}
}
else {
if (!uriComponents.getPath().startsWith(this.contextPath)) {
throw new IllegalArgumentException(uriComponents.getPath() + " should start with contextPath "
+ this.contextPath);
}
result.setContextPath(this.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));
}
}
Set<com.gargoylesoftware.htmlunit.util.Cookie> managedCookies = this.webClient.getCookies(this.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 = this.webRequest.getCharset();
if (charset == null) {
return "ISO-8859-1";
}
return charset;
}
private String header(String headerName) {
return this.webRequest.getAdditionalHeaders().get(headerName);
}
private void headers(MockHttpServletRequest result) {
for (Entry<String, String> header : this.webRequest.getAdditionalHeaders().entrySet()) {
result.addHeader(header.getKey(), header.getValue());
}
}
private MockHttpSession httpSession(MockHttpServletRequest request, final String sessionid) {
MockHttpSession session;
synchronized (this.sessions) {
session = this.sessions.get(sessionid);
if (session == null) {
session = new HtmlUnitMockHttpSession(request, sessionid);
session.setNew(true);
synchronized (this.sessions) {
this.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 : this.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 = this.webRequest.getUrl().getDefaultPort();
result.setLocalPort(serverPort);
result.setRemotePort(portConnection);
}
else {
result.setRemotePort(serverPort);
}
}
private UriComponents uriComponents() {
URL url = this.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.
*/
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 (HtmlUnitRequestBuilder.this.sessions) {
HtmlUnitRequestBuilder.this.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.
*/
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 (HtmlUnitRequestBuilder.this.sessions) {
HtmlUnitRequestBuilder.this.sessions.remove(getId());
}
removeSessionCookie(request, getId());
}
}
private CookieManager getCookieManager() {
return this.webClient.getCookieManager();
}
}

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

@ -0,0 +1,108 @@ @@ -0,0 +1,108 @@
/*
* 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.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcConfigurer;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
import com.gargoylesoftware.htmlunit.WebClient;
/**
* {@code MockMvcWebClientBuilder} simplifies the creation of a {@link WebClient}
* that delegates to a {@link MockMvc} instance.
*
* @author Rob Winch
* @author Sam Brannen
* @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);
}
/**
* Create a new instance with the supplied {@link WebApplicationContext}.
* @param context the {@code WebApplicationContext} to create a {@link MockMvc}
* instance from; never {@code null}
* @return the MockMvcWebClientBuilder to customize
*/
public static MockMvcWebClientBuilder webAppContextSetup(WebApplicationContext context) {
Assert.notNull(context, "WebApplicationContext must not be null");
return new MockMvcWebClientBuilder(context);
}
/**
* Create a new instance with the supplied {@link WebApplicationContext}
* and {@link MockMvcConfigurer}.
* @param context the {@code WebApplicationContext} to create a {@link MockMvc}
* instance from; never {@code null}
* @param configurer the MockMvcConfigurer to apply; never {@code null}
* @return the MockMvcWebClientBuilder to customize
*/
public static MockMvcWebClientBuilder webAppContextSetup(WebApplicationContext context, MockMvcConfigurer configurer) {
Assert.notNull(context, "WebApplicationContext must not be null");
Assert.notNull(configurer, "MockMvcConfigurer must not be null");
return new MockMvcWebClientBuilder(context, configurer);
}
/**
* Create a new instance with the supplied {@link MockMvc} instance.
* @param mockMvc the {@code MockMvc} instance to use; never {@code null}
* @return the MockMvcWebClientBuilder to customize
*/
public static MockMvcWebClientBuilder mockMvcSetup(MockMvc mockMvc) {
Assert.notNull(mockMvc, "MockMvc must not be null");
return new MockMvcWebClientBuilder(mockMvc);
}
/**
* Create a {@link WebClient} that uses the configured {@link MockMvc}
* instance for any matching requests and a {@code WebClient} with all
* the default settings for any other requests.
* @return the {@code WebClient} to use
* @see #configureWebClient(WebClient)
*/
public WebClient createWebClient() {
return configureWebClient(new WebClient());
}
/**
* Configure the supplied {@link WebClient} to use the configured
* {@link MockMvc} instance for any matching requests and the supplied
* {@code WebClient} for any other requests.
* @param webClient the WebClient to delegate to for requests that do not
* match; never {@code null}
* @return the WebClient to use
*/
public WebClient configureWebClient(WebClient webClient) {
Assert.notNull(webClient, "webClient must not be null");
webClient.setWebConnection(createConnection(webClient.getWebConnection()));
return webClient;
}
}

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

@ -0,0 +1,152 @@ @@ -0,0 +1,152 @@
/*
* 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 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;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebConnection;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
/**
* {@code MockMvcWebConnection} enables {@link MockMvc} to transform a
* {@link WebRequest} into a {@link WebResponse}.
* <p>This is the core integration with <a href="http://htmlunit.sourceforge.net/">HtmlUnit</a>.
* <p>Example usage can be seen below.
*
* <pre class="code">
* 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
* @author Sam Brannen
* @since 4.2
* @see WebConnectionHtmlUnitDriver
*/
public final class MockMvcWebConnection implements WebConnection {
private final Map<String, MockHttpSession> sessions = new HashMap<String, MockHttpSession>();
private final MockMvc mockMvc;
private final String contextPath;
private WebClient webClient;
/**
* Create a new instance that assumes the context path of the application
* is {@code ""} (i.e., the root context).
* <p>For example, the URL {@code http://localhost/test/this} would use
* {@code ""} as the context path.
* @param mockMvc the {@code MockMvc} instance to use; never {@code null}
*/
public MockMvcWebConnection(MockMvc mockMvc) {
this(mockMvc, "");
}
/**
* Create a new instance with the specified context path.
* <p>The path may be {@code null} in which case the first path segment
* of the URL is turned into the contextPath. Otherwise it must conform
* to {@link javax.servlet.http.HttpServletRequest#getContextPath()}
* which states that it can be an empty string and otherwise must start
* with a "/" character and not end with a "/" character.
* @param mockMvc the {@code MockMvc} instance to use; never {@code null}
* @param contextPath the contextPath to use
*/
public MockMvcWebConnection(MockMvc mockMvc, String contextPath) {
Assert.notNull(mockMvc, "mockMvc must not 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(this.sessions, this.webClient, webRequest);
requestBuilder.setContextPath(this.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 must not be null");
this.webClient = webClient;
}
private MockHttpServletResponse getResponse(RequestBuilder requestBuilder) throws IOException {
ResultActions resultActions;
try {
resultActions = this.mockMvc.perform(requestBuilder);
}
catch (Exception e) {
throw (IOException) new IOException(e.getMessage()).initCause(e);
}
return resultActions.andReturn().getResponse();
}
/**
* Validate the supplied {@code contextPath}.
* <p>If the value is not {@code null}, it must conform to
* {@link javax.servlet.http.HttpServletRequest#getContextPath()} which
* states that it can be an empty string and otherwise must start with
* a "/" character and not end with a "/" character.
* @param contextPath the path to validate
*/
static void validateContextPath(String contextPath) {
if (contextPath == null || "".equals(contextPath)) {
return;
}
if (!contextPath.startsWith("/")) {
throw new IllegalArgumentException("contextPath '" + contextPath + "' must start with '/'.");
}
if (contextPath.endsWith("/")) {
throw new IllegalArgumentException("contextPath '" + contextPath + "' must not end with '/'.");
}
}
}

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

@ -0,0 +1,165 @@ @@ -0,0 +1,165 @@
/*
* 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 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;
import com.gargoylesoftware.htmlunit.WebConnection;
/**
* Support class that simplifies the creation of a {@link WebConnection} that
* uses {@link MockMvc} and optionally delegates to a real {@link WebConnection}
* for specific requests.
*
* <p>The default is to use {@link MockMvc} for requests to {@code localhost}
* and otherwise use a real {@link WebConnection}.
*
* @author Rob Winch
* @author Sam Brannen
* @since 4.2
*/
public abstract class MockMvcWebConnectionBuilderSupport<T extends MockMvcWebConnectionBuilderSupport<T>> {
private final MockMvc mockMvc;
private final List<WebRequestMatcher> mockMvcRequestMatchers = new ArrayList<WebRequestMatcher>();
private String contextPath = "";
private boolean alwaysUseMockMvc;
/**
* Create a new instance using the supplied {@link MockMvc} instance.
* @param mockMvc the {@code MockMvc} instance to use; never {@code null}
*/
protected MockMvcWebConnectionBuilderSupport(MockMvc mockMvc) {
Assert.notNull(mockMvc, "mockMvc must not be null");
this.mockMvc = mockMvc;
this.mockMvcRequestMatchers.add(new HostRequestMatcher("localhost"));
}
/**
* Create a new instance using the supplied {@link WebApplicationContext}.
* @param context the {@code WebApplicationContext} to create a {@code MockMvc}
* instance from; never {@code null}
*/
protected MockMvcWebConnectionBuilderSupport(WebApplicationContext context) {
this(MockMvcBuilders.webAppContextSetup(context).build());
}
/**
* Create a new instance using the supplied {@link WebApplicationContext}
* and {@link MockMvcConfigurer}.
* @param context the {@code WebApplicationContext} to create a {@code MockMvc}
* instance from; never {@code null}
* @param configurer the MockMvcConfigurer to apply; never {@code null}
*/
protected MockMvcWebConnectionBuilderSupport(WebApplicationContext context, MockMvcConfigurer configurer) {
this(MockMvcBuilders.webAppContextSetup(context).apply(configurer).build());
}
/**
* Set the context path to use.
* <p>If the supplied value is {@code null} or empty, the first path
* segment of the request URL is assumed to be the context path.
* <p>Default is {@code ""}.
* @param contextPath the context path to use
* @return this builder for further customization
*/
@SuppressWarnings("unchecked")
public T contextPath(String contextPath) {
this.contextPath = contextPath;
return (T) this;
}
/**
* Specify that {@link MockMvc} should always be used regardless of
* what the request looks like.
* @return this builder for further customization
*/
@SuppressWarnings("unchecked")
public T alwaysUseMockMvc() {
this.alwaysUseMockMvc = true;
return (T) this;
}
/**
* Add additional {@link WebRequestMatcher} instances that will ensure
* that {@link MockMvc} is used to process the request, if such a matcher
* matches against the web request.
* @param matchers additional {@code WebRequestMatcher} instances
* @return this builder for further customization
*/
@SuppressWarnings("unchecked")
public T useMockMvc(WebRequestMatcher... matchers) {
for (WebRequestMatcher matcher : matchers) {
this.mockMvcRequestMatchers.add(matcher);
}
return (T) this;
}
/**
* Add additional {@link WebRequestMatcher} instances that return {@code true}
* if a supplied host matches &mdash; for example, {@code "example.com"} or
* {@code "example.com:8080"}.
* @param hosts additional hosts that ensure {@code MockMvc} gets invoked
* @return this builder for further customization
*/
@SuppressWarnings("unchecked")
public T useMockMvcForHosts(String... hosts) {
this.mockMvcRequestMatchers.add(new HostRequestMatcher(hosts));
return (T) this;
}
/**
* Create a new {@link WebConnection} that will use a {@link MockMvc}
* instance if one of the specified {@link WebRequestMatcher} instances
* matches.
* @param defaultConnection the default WebConnection to use if none of
* the specified {@code WebRequestMatcher} instances matches; never {@code null}
* @return a new {@code WebConnection} that will use a {@code MockMvc}
* instance if one of the specified {@code WebRequestMatcher} matches
* @see #alwaysUseMockMvc()
* @see #useMockMvc(WebRequestMatcher...)
* @see #useMockMvcForHosts(String...)
*/
protected final WebConnection createConnection(WebConnection defaultConnection) {
Assert.notNull(defaultConnection, "defaultConnection must not be null");
MockMvcWebConnection mockMvcWebConnection = new MockMvcWebConnection(this.mockMvc, this.contextPath);
if (this.alwaysUseMockMvc) {
return mockMvcWebConnection;
}
List<DelegatingWebConnection.DelegateWebConnection> delegates = new ArrayList<DelegatingWebConnection.DelegateWebConnection>(
this.mockMvcRequestMatchers.size());
for (WebRequestMatcher matcher : this.mockMvcRequestMatchers) {
delegates.add(new DelegatingWebConnection.DelegateWebConnection(matcher, mockMvcWebConnection));
}
return new DelegatingWebConnection(defaultConnection, delegates);
}
}

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

@ -0,0 +1,104 @@ @@ -0,0 +1,104 @@
/*
* 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 org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.WebResponseData;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
/**
* @author Rob Winch
* @author Sam Brannen
* @since 4.2
*/
final class MockWebResponseBuilder {
private static final String DEFAULT_STATUS_MESSAGE = "N/A";
private final long startTime;
private final WebRequest webRequest;
private final MockHttpServletResponse response;
public MockWebResponseBuilder(long startTime, WebRequest webRequest, MockHttpServletResponse response) {
Assert.notNull(webRequest, "webRequest must not be null");
Assert.notNull(response, "response must not be null");
this.startTime = startTime;
this.webRequest = webRequest;
this.response = response;
}
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 = (this.response.getRedirectedUrl() != null ? HttpStatus.MOVED_PERMANENTLY.value()
: this.response.getStatus());
String statusMessage = statusMessage(statusCode);
return new WebResponseData(this.response.getContentAsByteArray(), statusCode, statusMessage, responseHeaders);
}
private String statusMessage(int statusCode) {
String errorMessage = this.response.getErrorMessage();
if (StringUtils.hasText(errorMessage)) {
return errorMessage;
}
try {
return HttpStatus.valueOf(statusCode).getReasonPhrase();
}
catch (IllegalArgumentException ex) {
// ignore
}
return DEFAULT_STATUS_MESSAGE;
}
private List<NameValuePair> responseHeaders() {
Collection<String> headerNames = this.response.getHeaderNames();
List<NameValuePair> responseHeaders = new ArrayList<NameValuePair>(headerNames.size());
for (String headerName : headerNames) {
List<Object> headerValues = this.response.getHeaderValues(headerName);
for (Object value : headerValues) {
responseHeaders.add(new NameValuePair(headerName, String.valueOf(value)));
}
}
String location = this.response.getRedirectedUrl();
if (location != null) {
responseHeaders.add(new NameValuePair("Location", location));
}
return responseHeaders;
}
}

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

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
/*
* 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;
/**
* A {@link WebRequestMatcher} that allows matching on
* {@code WebRequest#getUrl().toExternalForm()} using a regular expression.
*
* <p>For example, if you would like to match on the domain {@code code.jquery.com},
* you might want to use the following.
*
* <pre class="code">WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*");</pre>
*
* @author Rob Winch
* @author Sam Brannen
* @since 4.2
* @see org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection
*/
public final class UrlRegexRequestMatcher implements WebRequestMatcher {
private final Pattern pattern;
public UrlRegexRequestMatcher(String regex) {
this.pattern = Pattern.compile(regex);
}
public UrlRegexRequestMatcher(Pattern pattern) {
this.pattern = pattern;
}
@Override
public boolean matches(WebRequest request) {
String url = request.getUrl().toExternalForm();
return this.pattern.matcher(url).matches();
}
}

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

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
/*
* 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 for matching on a {@link WebRequest}.
*
* @author Rob Winch
* @since 4.2
*/
public interface WebRequestMatcher {
/**
* Whether this matcher matches on the supplied web request.
* @param request the {@link WebRequest} to attempt to match on
* @return {@code true} if this matcher matches on the {@code WebRequest}
*/
boolean matches(WebRequest request);
}

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

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
/**
* Server-side support for testing Spring MVC applications with {@code MockMvc}
* and HtmlUnit.
* @see org.springframework.test.web.servlet.MockMvc
*/
package org.springframework.test.web.servlet.htmlunit;

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

@ -0,0 +1,123 @@ @@ -0,0 +1,123 @@
/*
* 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 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;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.WebClient;
/**
* Convenience class for building an {@link HtmlUnitDriver} that delegates
* to {@link MockMvc} and optionally delegates to an actual connection for
* specific requests.
*
* <p>By default, the driver will delegate to {@code MockMvc} to handle
* requests to {@code localhost} and to a {@link WebClient} to handle any
* other URL (i.e. to perform an actual HTTP request).
*
* @author Rob Winch
* @author Sam Brannen
* @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);
}
/**
* Create a new instance using the supplied {@link WebApplicationContext}.
* @param context the WebApplicationContext to use; never {@code null}
* @return the MockMvcHtmlUnitDriverBuilder to customize
*/
public static MockMvcHtmlUnitDriverBuilder webAppContextSetup(WebApplicationContext context) {
return new MockMvcHtmlUnitDriverBuilder(context);
}
/**
* Create a new instance using the supplied {@link WebApplicationContext}
* and {@link MockMvcConfigurer}.
* @param context the WebApplicationContext to create a MockMvc instance from;
* never {@code null}
* @param configurer the MockMvcConfigurer to apply; never {@code null}
* @return the MockMvcHtmlUnitDriverBuilder to customize
*/
public static MockMvcHtmlUnitDriverBuilder webAppContextSetup(WebApplicationContext context,
MockMvcConfigurer configurer) {
return new MockMvcHtmlUnitDriverBuilder(context, configurer);
}
/**
* Create a new instance using the supplied {@link MockMvc} instance.
* @param mockMvc the MockMvc instance to use; never {@code null}
* @return the MockMvcHtmlUnitDriverBuilder to customize
*/
public static MockMvcHtmlUnitDriverBuilder mockMvcSetup(MockMvc mockMvc) {
return new MockMvcHtmlUnitDriverBuilder(mockMvc);
}
/**
* Specify whether JavaScript should be enabled.
* <p>Default is {@code 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;
}
/**
* Create a new {@link HtmlUnitDriver} with the {@link BrowserVersion}
* set to {@link BrowserVersion#CHROME CHROME}.
* <p>For additional configuration options, use {@link #configureDriver}.
* @return the {@code HtmlUnitDriver} to use
* @see #configureDriver(WebConnectionHtmlUnitDriver)
*/
public HtmlUnitDriver createDriver() {
return configureDriver(new WebConnectionHtmlUnitDriver(BrowserVersion.CHROME));
}
/**
* Configure an existing {@link WebConnectionHtmlUnitDriver}.
* @param driver the WebConnectionHtmlUnitDriver to configure
* @return the {@code HtmlUnitDriver} to use
*/
public HtmlUnitDriver configureDriver(WebConnectionHtmlUnitDriver driver) {
driver.setJavascriptEnabled(javascriptEnabled);
driver.setWebConnection(createConnection(driver.getWebConnection()));
return driver;
}
}

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

@ -0,0 +1,106 @@ @@ -0,0 +1,106 @@
/*
* 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 org.openqa.selenium.Capabilities;
import org.openqa.selenium.htmlunit.HtmlUnitDriver;
import org.springframework.test.web.servlet.htmlunit.MockMvcWebConnection;
import org.springframework.util.Assert;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebConnection;
/**
* {@code WebConnectionHtmlUnitDriver} enables configuration of the
* {@link WebConnection} for an {@link HtmlUnitDriver} instance.
*
* <p>This is useful because it allows a {@link MockMvcWebConnection} to
* be injected.
*
* @author Rob Winch
* @author Sam Brannen
* @since 4.2
* @see MockMvcHtmlUnitDriverBuilder
*/
public class WebConnectionHtmlUnitDriver extends HtmlUnitDriver {
private WebClient webClient;
public WebConnectionHtmlUnitDriver(BrowserVersion browserVersion) {
super(browserVersion);
}
public WebConnectionHtmlUnitDriver() {
}
public WebConnectionHtmlUnitDriver(boolean enableJavascript) {
super(enableJavascript);
}
public WebConnectionHtmlUnitDriver(Capabilities capabilities) {
super(capabilities);
}
/**
* Modify the supplied {@link WebClient}, {@linkplain #configureWebClient
* configure} it, and retain a reference to it so that its {@link WebConnection}
* is {@linkplain #getWebConnection accessible} for later use.
* @param client the client to modify
* @return the modified client
* @see org.openqa.selenium.htmlunit.HtmlUnitDriver#modifyWebClient(WebClient)
*/
@Override
protected final WebClient modifyWebClient(WebClient webClient) {
this.webClient = super.modifyWebClient(webClient);
this.webClient = configureWebClient(this.webClient);
return this.webClient;
}
/**
* Configure the supplied {@link WebClient}.
* <p>The default implementation simply returns the supplied client
* unmodified.
* <p>Subclasses can override this method to customize the {@code WebClient}
* that the {@link HtmlUnitDriver} driver uses.
* @param client the client to configure
* @return the configured client
*/
protected WebClient configureWebClient(WebClient client) {
return client;
}
/**
* Access the current {@link WebConnection} for the {@link WebClient}.
* @return the current {@code WebConnection}
*/
public WebConnection getWebConnection() {
return this.webClient.getWebConnection();
}
/**
* Set the {@link WebConnection} to be used with the {@link WebClient}.
* @param webConnection the {@code WebConnection} to use; never {@code null}
*/
public void setWebConnection(WebConnection webConnection) {
Assert.notNull(webConnection, "WebConnection must not 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 @@
/**
* Server-side support for testing Spring MVC applications with {@code MockMvc}
* and the Selenium {@code HtmlUnitDriver}.
* @see org.springframework.test.web.servlet.MockMvc
* @see org.openqa.selenium.htmlunit.HtmlUnitDriver
*/
package org.springframework.test.web.servlet.htmlunit.webdriver;

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

@ -0,0 +1,143 @@ @@ -0,0 +1,143 @@
/*
* 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 org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.stereotype.Controller;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection.DelegateWebConnection;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
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 static org.mockito.Mockito.*;
/**
* @author Rob Winch
* @since 4.2
*/
@RunWith(MockitoJUnitRunner.class)
public class DelegatingWebConnectionTests {
@Mock
private WebRequestMatcher matcher1;
@Mock
private WebRequestMatcher matcher2;
@Mock
private WebConnection defaultConnection;
@Mock
private WebConnection connection1;
@Mock
private WebConnection connection2;
private DelegatingWebConnection webConnection;
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 {}
}

34
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/ForwardController.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 org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
* @author Rob Winch
* @since 4.2
*/
@Controller
public class ForwardController {
@RequestMapping("/forward")
public String forward() {
return "forward:/";
}
}

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

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
/*
* 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
* @since 4.2
*/
@Controller
public class HelloController {
@RequestMapping
@ResponseBody
public String header(HttpServletRequest request) {
return "hello";
}
}

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

@ -0,0 +1,81 @@ @@ -0,0 +1,81 @@
/*
* 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 org.junit.Test;
import com.gargoylesoftware.htmlunit.WebRequest;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* @author Rob Winch
* @since 4.2
*/
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));;
}
}

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

@ -0,0 +1,785 @@ @@ -0,0 +1,785 @@
/*
* 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 org.apache.commons.io.IOUtils;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.junit.Before;
import org.junit.Test;
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;
import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
/**
* @author Rob Winch
* @since 4.2
*/
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");
}
}

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

@ -0,0 +1,162 @@ @@ -0,0 +1,162 @@
/*
* 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 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;
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 static org.mockito.Mockito.mock;
/**
* @author Rob Winch
* @since 4.2
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
@SuppressWarnings("rawtypes")
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();
}
}
}
}

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

@ -0,0 +1,123 @@ @@ -0,0 +1,123 @@
/*
* 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 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;
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;
/**
* @author Rob Winch
* @since 4.2
*/
@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";
}
}
}
}

97
spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.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 java.io.IOException;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import com.gargoylesoftware.htmlunit.Page;
import com.gargoylesoftware.htmlunit.WebClient;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
/**
* @author Rob Winch
* @since 4.2
*/
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/");
}
}

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

@ -0,0 +1,138 @@ @@ -0,0 +1,138 @@
/*
* 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 org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletResponse;
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;
/**
* @author Rob Winch
* @since 4.2
*/
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));;
}
}

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

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
/*
* 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 org.junit.Test;
import com.gargoylesoftware.htmlunit.WebRequest;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
/**
* @author Rob Winch
* @since 4.2
*/
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));
}
}

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

@ -0,0 +1,138 @@ @@ -0,0 +1,138 @@
/*
* 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
* @since 4.2
*/
@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;
}
}
}
}

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

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
/*
* 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 org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
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 static org.mockito.Matchers.any;
import static org.mockito.Mockito.when;
/**
* @author Rob Winch
* @since 4.2
*/
@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

4
src/asciidoc/whats-new.adoc

@ -562,6 +562,10 @@ public @interface MyTestConfig { @@ -562,6 +562,10 @@ public @interface MyTestConfig {
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
----
* The Spring MVC Test framework now provides first-class support for HtmlUnit,
including integration with Selenium's WebDriver, allowing for page-based
web application testing without the need to deploy to a Servlet container.
** See <<spring-mvc-test-server-htmlunit>> for details.
* `AopTestUtils` is a new testing utility that allows developers to
obtain a reference to the underlying target object hidden behind one
or more Spring proxies.

Loading…
Cancel
Save