From b73e39423c6b44f4cc30a38528bbf5d162754f51 Mon Sep 17 00:00:00 2001 From: Rob Winch Date: Tue, 23 Jun 2015 11:31:48 -0500 Subject: [PATCH 1/5] Introduce support for HtmlUnit in Spring MVC Test This commit introduces integration between MockMvc and HtmlUnit, thus simplifying end-to-end testing when using HTML-based views and enabling developers to do the following. - Easily test HTML pages using tools such as HtmlUnit, WebDriver, & Geb without the need to deploy to a Servlet container - Test JavaScript within pages - Optionally test using mock services to speed up testing - Share logic between in-container, end-to-end tests and out-of-container integration tests Issue: SPR-13158 --- build.gradle | 4 + .../htmlunit/DelegatingWebConnection.java | 93 +++ .../htmlunit/ForwardRequestPostProcessor.java | 39 + .../servlet/htmlunit/HostRequestMatcher.java | 92 +++ .../htmlunit/HtmlUnitRequestBuilder.java | 523 ++++++++++++ .../htmlunit/MockMvcWebClientBuilder.java | 97 +++ .../htmlunit/MockMvcWebConnection.java | 150 ++++ .../MockMvcWebConnectionBuilderSupport.java | 156 ++++ .../htmlunit/MockWebResponseBuilder.java | 93 +++ .../htmlunit/UrlRegexRequestMatcher.java | 50 ++ .../servlet/htmlunit/WebRequestMatcher.java | 34 + .../web/servlet/htmlunit/package-info.java | 7 + .../MockMvcHtmlUnitDriverBuilder.java | 114 +++ .../WebConnectionHtmlUnitDriver.java | 96 +++ .../htmlunit/webdriver/package-info.java | 7 + .../DelegatingWebConnectionTests.java | 137 +++ .../servlet/htmlunit/ForwardController.java | 31 + .../web/servlet/htmlunit/HelloController.java | 34 + .../htmlunit/HostRequestMatcherTests.java | 79 ++ .../htmlunit/HtmlUnitRequestBuilderTests.java | 782 ++++++++++++++++++ .../MockMvcConnectionBuilderSupportTests.java | 155 ++++ .../MockMvcWebClientBuilderTests.java | 117 +++ .../htmlunit/MockMvcWebConnectionTests.java | 92 +++ .../htmlunit/MockWebResponseBuilderTests.java | 135 +++ .../htmlunit/UrlRegexRequestMatcherTests.java | 43 + .../MockMvcHtmlUnitDriverBuilderTests.java | 136 +++ .../WebConnectionHtmlUnitDriverTests.java | 55 ++ src/asciidoc/testing.adoc | 772 +++++++++++++++++ 28 files changed, 4123 insertions(+) create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/ForwardRequestPostProcessor.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcher.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcher.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/WebRequestMatcher.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/package-info.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java create mode 100644 spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/package-info.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/ForwardController.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HelloController.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcherTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcherTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriverTests.java diff --git a/build.gradle b/build.gradle index aeae50994f7..7459e7cb76f 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,7 @@ configure(allprojects) { project -> ext.httpclientVersion = "4.5" ext.httpasyncVersion = "4.1" ext.jackson2Version = "2.6.0" + ext.htmlunitVersion = "2.17" ext.jasperreportsVersion = "6.1.0" ext.javamailVersion = "1.5.4" ext.jettyVersion = "9.3.1.v20150714" @@ -56,6 +57,7 @@ configure(allprojects) { project -> 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") { 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}") diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java new file mode 100644 index 00000000000..cd03c96786c --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import com.gargoylesoftware.htmlunit.WebConnection; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; + +import org.springframework.util.Assert; + +/** + *

+ * Implementation of WebConnection that allows delegating to various WebConnection implementations. For example, if + * you host your JavaScript on the domain code.jquery.com, you might want to use the following:

+ *
+ * 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);
+ * 
+ * @author Rob Winch + * @since 4.2 + */ +public final class DelegatingWebConnection implements WebConnection { + private final List connections; + private final WebConnection defaultConnection; + + public DelegatingWebConnection(WebConnection defaultConnection, List connections) { + Assert.notNull(defaultConnection, "defaultConnection cannot be null"); + Assert.notEmpty(connections, "connections cannot be empty"); + this.connections = connections; + this.defaultConnection = defaultConnection; + } + + public DelegatingWebConnection(WebConnection defaultConnection,DelegateWebConnection... connections) { + this(defaultConnection, Arrays.asList(connections)); + } + + @Override + public WebResponse getResponse(WebRequest request) throws IOException { + for(DelegateWebConnection connection : connections) { + if(connection.getMatcher().matches(request)) { + return connection.getDelegate().getResponse(request); + } + } + return defaultConnection.getResponse(request); + } + + public final static class DelegateWebConnection { + private final WebRequestMatcher matcher; + private final WebConnection delegate; + + public DelegateWebConnection(WebRequestMatcher matcher, WebConnection delegate) { + this.matcher = matcher; + this.delegate = delegate; + } + + private WebRequestMatcher getMatcher() { + return matcher; + } + + private WebConnection getDelegate() { + return delegate; + } + } +} \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/ForwardRequestPostProcessor.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/ForwardRequestPostProcessor.java new file mode 100644 index 00000000000..93a5f5d75cf --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/ForwardRequestPostProcessor.java @@ -0,0 +1,39 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.web.servlet.request.RequestPostProcessor; +import org.springframework.util.Assert; + +/** + * @author Rob Winch + * @since 4.2 + */ +final class ForwardRequestPostProcessor implements RequestPostProcessor { + private final String forwardUrl; + + public ForwardRequestPostProcessor(String url) { + Assert.hasText(url, "Forward url must have text"); + forwardUrl = url; + } + + @Override + public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { + request.setServletPath(forwardUrl); + return request; + } +} diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcher.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcher.java new file mode 100644 index 00000000000..98995550385 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcher.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import java.net.URL; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import com.gargoylesoftware.htmlunit.WebRequest; + +/** + *

+ * An implementation of WebRequestMatcher that allows matching on the host and optionally + * the port of WebRequest#getUrl(). For example, the following would match any request to + * the host "code.jquery.com" without regard for the port: + *

+ * + *
+ * WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com");
+ * 
+ * + * Multiple hosts can also be passed in. For example, the following would match an request + * to the host "code.jquery.com" or the host "cdn.com" without regard for the port: + * + *
+ * WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com", "cdn.com");
+ * 
+ * + *

+ * Alternatively, one can also specify the port. For example, the following would match + * any request to the host "code.jquery.com" with the port of 80. + *

+ * + *
+ * WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com:80");
+ * 
+ * + *

+ * The above cdnMatcher would match: "http://code.jquery.com/jquery.js" (default port of + * 80) and "http://code.jquery.com:80/jquery.js". However, it would not match + * "https://code.jquery.com/jquery.js" (default port of 443). + *

+ * + * @author Rob Winch + * @since 4.2 + * @see UrlRegexRequestMatcher + * @see org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection + */ +public final class HostRequestMatcher implements WebRequestMatcher { + private final Set hosts = new HashSet(); + + /** + * Creates a new instance + * + * @param hosts the hosts to match on (i.e. "localhost", "example.com:443") + */ + public HostRequestMatcher(String... hosts) { + this.hosts.addAll(Arrays.asList(hosts)); + } + + @Override + public boolean matches(WebRequest request) { + URL url = request.getUrl(); + String host = url.getHost(); + + if(hosts.contains(host)) { + return true; + } + + int port = url.getPort(); + if(port == -1) { + port = url.getDefaultPort(); + } + String hostAndPort = host + ":" + port; + + return hosts.contains(hostAndPort); + } +} diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java new file mode 100644 index 00000000000..e8e48af01f7 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java @@ -0,0 +1,523 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.ServletContext; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import com.gargoylesoftware.htmlunit.CookieManager; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.util.NameValuePair; + +import org.springframework.beans.Mergeable; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.SmartRequestBuilder; +import org.springframework.test.web.servlet.request.RequestPostProcessor; +import org.springframework.util.Assert; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +/** + *

+ * Internal class used to allow a {@link WebRequest} into a {@link MockHttpServletRequest} using Spring MVC Test's + * {@link RequestBuilder}. + *

+ *

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

+ * Allows {@link MockMvc} to transform a {@link WebRequest} into a {@link WebResponse}. This is the core integration + * with HTML Unit. + *

+ *

+ * Example usage can be seen below: + *

+ * + *
+ * WebClient webClient = new WebClient();
+ * MockMvc mockMvc = ...
+ * MockMvcWebConnection webConnection = new MockMvcWebConnection(mockMvc);
+ * mockConnection.setWebClient(webClient);
+ * webClient.setWebConnection(webConnection);
+ *
+ * ... use webClient as normal ...
+ * 
+ * + * @author Rob Winch + * @since 4.2 + * @see WebConnectionHtmlUnitDriver + */ +public final class MockMvcWebConnection implements WebConnection { + private WebClient webClient; + + private final Map sessions = new HashMap(); + + private final MockMvc mockMvc; + + private final String contextPath; + + /** + * Creates a new instance that assumes the context root of the application is "". For example, + * the URL http://localhost/test/this would use "" as the context root. + * + * @param mockMvc the MockMvc instance to use + */ + public MockMvcWebConnection(MockMvc mockMvc) { + this(mockMvc, ""); + } + + /** + * Creates a new instance with a specified context root. + * + * @param mockMvc the MockMvc instance to use + * @param contextPath the contextPath to use. The value may be null in which case the first path segment of the URL is turned + * into the contextPath. Otherwise it must conform to {@link HttpServletRequest#getContextPath()} which states it + * can be empty string or it must start with a "/" and not end in a "/". + */ + public MockMvcWebConnection(MockMvc mockMvc, String contextPath) { + Assert.notNull(mockMvc, "mockMvc cannot be null"); + validateContextPath(contextPath); + + this.webClient = new WebClient(); + this.mockMvc = mockMvc; + this.contextPath = contextPath; + } + + public WebResponse getResponse(WebRequest webRequest) throws IOException { + long startTime = System.currentTimeMillis(); + HtmlUnitRequestBuilder requestBuilder = new HtmlUnitRequestBuilder(sessions, webClient, webRequest); + requestBuilder.setContextPath(contextPath); + + MockHttpServletResponse httpServletResponse = getResponse(requestBuilder); + + String forwardedUrl = httpServletResponse.getForwardedUrl(); + while(forwardedUrl != null) { + requestBuilder.setForwardPostProcessor(new ForwardRequestPostProcessor(forwardedUrl)); + httpServletResponse = getResponse(requestBuilder); + forwardedUrl = httpServletResponse.getForwardedUrl(); + } + + return new MockWebResponseBuilder(startTime, webRequest, httpServletResponse).build(); + } + + public void setWebClient(WebClient webClient) { + Assert.notNull(webClient, "webClient cannot be null"); + this.webClient = webClient; + } + + private CookieManager getCookieManager() { + return webClient.getCookieManager(); + } + + private MockHttpServletResponse getResponse(RequestBuilder requestBuilder) throws IOException { + ResultActions resultActions; + try { + resultActions = mockMvc.perform(requestBuilder); + } + catch (Exception e) { + throw (IOException) new IOException(e.getMessage()).initCause(e); + } + + return resultActions.andReturn().getResponse(); + } + + /** + * Performs validation on the contextPath + * + * @param contextPath the contextPath to validate + */ + private static void validateContextPath(String contextPath) { + if (contextPath == null || "".equals(contextPath)) { + return; + } + if (contextPath.endsWith("/")) { + throw new IllegalArgumentException("contextPath cannot end with /. Got '" + contextPath + "'"); + } + if (!contextPath.startsWith("/")) { + throw new IllegalArgumentException("contextPath must start with /. Got '" + contextPath + "'"); + } + } +} \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java new file mode 100644 index 00000000000..df2055fd45a --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java @@ -0,0 +1,156 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import java.util.ArrayList; +import java.util.List; + +import com.gargoylesoftware.htmlunit.WebConnection; + +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.test.web.servlet.setup.MockMvcConfigurer; +import org.springframework.util.Assert; +import org.springframework.web.context.WebApplicationContext; + +/** + * Makes it easy to create a WebConnection that uses MockMvc and optionally delegates to + * a real WebConnection for specific requests. The default is to use MockMvc for any host + * that is "localhost" and otherwise use a real WebConnection. + * + * @author Rob Winch + * @since 4.2 + */ +public abstract class MockMvcWebConnectionBuilderSupport> { + private String contextPath = ""; + + private final MockMvc mockMvc; + + private List mockMvcRequestMatchers = new ArrayList(); + + private boolean alwaysUseMockMvc; + + /** + * Creates a new instance using a MockMvc instance + * + * @param mockMvc the MockMvc instance to use. Cannot be null. + */ + protected MockMvcWebConnectionBuilderSupport(MockMvc mockMvc) { + Assert.notNull(mockMvc, "mockMvc cannot be null"); + this.mockMvc = mockMvc; + this.mockMvcRequestMatchers.add(new HostRequestMatcher("localhost")); + } + + /** + * Creates a new instance using a WebApplicationContext + * @param context the WebApplicationContext to create a MockMvc instance from. + * Cannot be null. + */ + protected MockMvcWebConnectionBuilderSupport(WebApplicationContext context) { + this(MockMvcBuilders.webAppContextSetup(context).build()); + } + + /** + * Creates a new instance using a WebApplicationContext + * @param context the WebApplicationContext to create a MockMvc instance from. + * @param configurer the MockMvcConfigurer to apply + * Cannot be null. + */ + protected MockMvcWebConnectionBuilderSupport(WebApplicationContext context, MockMvcConfigurer configurer) { + this(MockMvcBuilders.webAppContextSetup(context).apply(configurer).build()); + } + + /** + * The context path to use. Default is "". If the value is null, then the first path + * segment of the request URL is assumed to be the context path. + * + * @param contextPath the context path to use. + * @return the builder for further customization + */ + @SuppressWarnings("unchecked") + public T contextPath(String contextPath) { + this.contextPath = contextPath; + return (T) this; + } + + /** + * Always use MockMvc no matter what the request looks like. + * + * @return the builder for further customization + */ + @SuppressWarnings("unchecked") + public T alwaysUseMockMvc() { + this.alwaysUseMockMvc = true; + return (T) this; + } + + /** + * Add additional WebRequestMatcher instances that if return true will ensure MockMvc + * is used. + * + * @param matchers the WebRequestMatcher instances that if true will ensure MockMvc + * processes the request. + * @return the builder for further customization + */ + @SuppressWarnings("unchecked") + public T useMockMvc(WebRequestMatcher... matchers) { + for(WebRequestMatcher matcher : matchers) { + this.mockMvcRequestMatchers.add(matcher); + } + return (T) this; + } + + /** + * Add additional WebRequestMatcher instances that will return true if the host matches. + * + * @param hosts the additional hosts that will ensure MockMvc gets invoked (i.e. example.com or example.com:8080). + * @return the builder for further customization + */ + @SuppressWarnings("unchecked") + public T useMockMvcForHosts(String... hosts) { + this.mockMvcRequestMatchers.add(new HostRequestMatcher(hosts)); + return (T) this; + } + + /** + * Creates a new WebConnection that will use a MockMvc instance if one of the + * specified WebRequestMatcher matches. + * + * @param defaultConnection the default WebConnection to use if none of the specified + * WebRequestMatcher instances match. Cannot be null. + * @return a new WebConnection that will use a MockMvc instance if one of the + * specified WebRequestMatcher matches. + * + * @see #alwaysUseMockMvc + * @see #useMockMvc(WebRequestMatcher...) + * @see #useMockMvcForHosts(String...) + */ + protected final WebConnection createConnection(WebConnection defaultConnection) { + Assert.notNull(defaultConnection, "defaultConnection cannot be null"); + MockMvcWebConnection mockMvcWebConnection = new MockMvcWebConnection(mockMvc, contextPath); + + if(alwaysUseMockMvc) { + return mockMvcWebConnection; + } + + List delegates = new ArrayList(mockMvcRequestMatchers.size()); + for(WebRequestMatcher matcher : mockMvcRequestMatchers) { + delegates.add(new DelegatingWebConnection.DelegateWebConnection(matcher, mockMvcWebConnection)); + } + + return new DelegatingWebConnection(defaultConnection, delegates); + } +} \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java new file mode 100644 index 00000000000..f619641bb63 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java @@ -0,0 +1,93 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.WebResponseData; +import com.gargoylesoftware.htmlunit.util.NameValuePair; + +import org.springframework.http.HttpStatus; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.util.Assert; + +/** + * @author Rob Winch + * @since 4.2 + */ +final class MockWebResponseBuilder { + private final long startTime; + + private final WebRequest webRequest; + + private final MockHttpServletResponse response; + + public MockWebResponseBuilder(long startTime, WebRequest webRequest, MockHttpServletResponse httpServletResponse) { + Assert.notNull(webRequest, "webRequest"); + Assert.notNull(httpServletResponse, "httpServletResponse cannot be null"); + this.startTime = startTime; + this.webRequest = webRequest; + this.response = httpServletResponse; + } + + public WebResponse build() throws IOException { + WebResponseData webResponseData = webResponseData(); + long endTime = System.currentTimeMillis(); + return new WebResponse(webResponseData, webRequest, endTime - startTime); + } + + private WebResponseData webResponseData() throws IOException { + List responseHeaders = responseHeaders(); + int statusCode = response.getRedirectedUrl() == null ? response.getStatus() : 301; + String statusMessage = statusMessage(statusCode); + return new WebResponseData(response.getContentAsByteArray(), statusCode, statusMessage, responseHeaders); + } + + private String statusMessage(int statusCode) { + String errorMessage = response.getErrorMessage(); + if (errorMessage != null) { + return errorMessage; + } + try { + return HttpStatus.valueOf(statusCode).getReasonPhrase(); + } + catch (IllegalArgumentException useDefault) { + } + ; + return "N/A"; + } + + private List responseHeaders() { + Collection headerNames = response.getHeaderNames(); + List responseHeaders = new ArrayList(headerNames.size()); + for (String headerName : headerNames) { + List headerValues = response.getHeaderValues(headerName); + for (Object value : headerValues) { + responseHeaders.add(new NameValuePair(headerName, String.valueOf(value))); + } + } + String location = response.getRedirectedUrl(); + if (location != null) { + responseHeaders.add(new NameValuePair("Location", location)); + } + return responseHeaders; + } +} diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcher.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcher.java new file mode 100644 index 00000000000..c4593bd4d8b --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcher.java @@ -0,0 +1,50 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import java.util.regex.Pattern; + +import com.gargoylesoftware.htmlunit.WebRequest; + +/** + *

+ * An implementation of WebRequestMatcher that allows matching on WebRequest#getUrl().toExternalForm() using a regular expression. For example, if you would like to match on the domain code.jquery.com, you might want to use the following:

+ * + *
+ * WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*");
+ * 
+ * + * @author Rob Winch + * @since 4.2 + * @see org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection + */ +public final class UrlRegexRequestMatcher implements WebRequestMatcher { + private Pattern pattern; + + public UrlRegexRequestMatcher(String regex) { + pattern = Pattern.compile(regex); + } + + public UrlRegexRequestMatcher(Pattern pattern) { + this.pattern = pattern; + } + + @Override + public boolean matches(WebRequest request) { + String url = request.getUrl().toExternalForm(); + return pattern.matcher(url).matches(); + } +} diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/WebRequestMatcher.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/WebRequestMatcher.java new file mode 100644 index 00000000000..034295490aa --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/WebRequestMatcher.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + package org.springframework.test.web.servlet.htmlunit; + +import com.gargoylesoftware.htmlunit.WebRequest; + +/** + * Strategy to match on a WebRequest + * + * @author Rob Winch + * @since 4.2 + */ +public interface WebRequestMatcher { + /** + * Return true if matches on WebRequest, else false + * + * @param request the WebRequest to attempt to match on + * @return true if matches on WebRequest, else false + */ + boolean matches(WebRequest request); +} diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/package-info.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/package-info.java new file mode 100644 index 00000000000..40810e0127f --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/package-info.java @@ -0,0 +1,7 @@ +/** + * Support for MockMvc and HtmlUnit integration + * + * @author Rob Winch + * @since 4.2 + */ +package org.springframework.test.web.servlet.htmlunit; \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java new file mode 100644 index 00000000000..a732863c111 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java @@ -0,0 +1,114 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit.webdriver; + +import com.gargoylesoftware.htmlunit.BrowserVersion; +import org.openqa.selenium.htmlunit.HtmlUnitDriver; + +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.htmlunit.MockMvcWebConnectionBuilderSupport; +import org.springframework.test.web.servlet.setup.MockMvcConfigurer; +import org.springframework.web.context.WebApplicationContext; + +/** + * Convenience class for building an HtmlUnitDriver that will delegate to MockMvc and + * optionally delegate to an actual connection for specific requests. + * + * By default localhost will delegate to MockMvc and any other URL will delegate + * + * @author Rob Winch + * @since 4.2 + */ +public class MockMvcHtmlUnitDriverBuilder extends MockMvcWebConnectionBuilderSupport { + private boolean javascriptEnabled = true; + + protected MockMvcHtmlUnitDriverBuilder(MockMvc mockMvc) { + super(mockMvc); + } + + protected MockMvcHtmlUnitDriverBuilder(WebApplicationContext context) { + super(context); + } + + protected MockMvcHtmlUnitDriverBuilder(WebApplicationContext context, MockMvcConfigurer configurer) { + super(context, configurer); + } + + /** + * Creates a new instance with a WebApplicationContext. + * + * @param context the WebApplicationContext to use. Cannot be null. + * @return the MockMvcHtmlUnitDriverBuilder to customize + */ + public static MockMvcHtmlUnitDriverBuilder webAppContextSetup(WebApplicationContext context) { + return new MockMvcHtmlUnitDriverBuilder(context); + } + + /** + * Creates a new instance using a WebApplicationContext + * @param context the WebApplicationContext to create a MockMvc instance from. + * @param configurer the MockMvcConfigurer to apply + * Cannot be null. + * @return the MockMvcHtmlUnitDriverBuilder to customize + */ + public static MockMvcHtmlUnitDriverBuilder webAppContextSetup(WebApplicationContext context, MockMvcConfigurer configurer) { + return new MockMvcHtmlUnitDriverBuilder(context, configurer); + } + + /** + * Creates a new instance with a MockMvc instance. + * + * @param mockMvc the MockMvc to use. Cannot be null. + * @return the MockMvcHtmlUnitDriverBuilder to customize + */ + public static MockMvcHtmlUnitDriverBuilder mockMvcSetup(MockMvc mockMvc) { + return new MockMvcHtmlUnitDriverBuilder(mockMvc); + } + + /** + * Specifies if JavaScript should be enabled or not. Default is true. + * + * @param javascriptEnabled if JavaScript should be enabled or not. + * @return the builder for further customizations + */ + public MockMvcHtmlUnitDriverBuilder javascriptEnabled(boolean javascriptEnabled) { + this.javascriptEnabled = javascriptEnabled; + return this; + } + + /** + * Creates a new HtmlUnitDriver with the BrowserVersion set to CHROME. For additional + * configuration options, use configureDriver. + * + * @return the HtmlUnitDriver to use + * @see #configureDriver(WebConnectionHtmlUnitDriver) + */ + public HtmlUnitDriver createDriver() { + return configureDriver(new WebConnectionHtmlUnitDriver(BrowserVersion.CHROME)); + } + + /** + * Configures an existing WebConnectionHtmlUnitDriver. + * + * @param driver the WebConnectionHtmlUnitDriver to configure + * @return the HtmlUnitDriver to use + */ + public HtmlUnitDriver configureDriver(WebConnectionHtmlUnitDriver driver) { + driver.setJavascriptEnabled(javascriptEnabled); + driver.setWebConnection(createConnection(driver.getWebConnection())); + return driver; + } +} \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java new file mode 100644 index 00000000000..8b48e209212 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit.webdriver; + +import com.gargoylesoftware.htmlunit.BrowserVersion; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebConnection; +import org.openqa.selenium.Capabilities; +import org.openqa.selenium.htmlunit.HtmlUnitDriver; + +import org.springframework.util.Assert; + +/** + *

+ * Allows configuring the WebConnection for an HtmlUnitDriver instance. This is useful + * because it allows a MockMvcWebConnection to be injected. + *

+ * + * @author Rob Winch + * @since 4.2 + * @see MockMvcHtmlUnitDriverBuilder + */ +public class WebConnectionHtmlUnitDriver extends HtmlUnitDriver { + private WebClient webClient; + + public WebConnectionHtmlUnitDriver(BrowserVersion version) { + super(version); + } + + public WebConnectionHtmlUnitDriver() { + } + + public WebConnectionHtmlUnitDriver(boolean enableJavascript) { + super(enableJavascript); + } + + public WebConnectionHtmlUnitDriver(Capabilities capabilities) { + super(capabilities); + } + + /** + * Captures the WebClient that is used so that its WebConnection is accessible. + * + * @param client The client to modify + * @return The modified client + */ + @Override + protected final WebClient modifyWebClient(WebClient client) { + webClient = super.modifyWebClient(client); + webClient = configureWebClient(webClient); + return webClient; + } + + /** + * Subclasses can override this method to customise the WebClient that the HtmlUnit + * driver uses. + * + * @param client The client to modify + * @return The modified client + */ + protected WebClient configureWebClient(WebClient client) { + return client; + } + + /** + * Allows accessing the current WebConnection + * + * @return the current WebConnection + */ + public WebConnection getWebConnection() { + return webClient.getWebConnection(); + } + + /** + * Sets the WebConnection to be used. + * + * @param webConnection the WebConnection to use. Cannot be null. + */ + public void setWebConnection(WebConnection webConnection) { + Assert.notNull(webConnection, "webConnection cannot be null"); + this.webClient.setWebConnection(webConnection); + } +} \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/package-info.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/package-info.java new file mode 100644 index 00000000000..9b4861d7e29 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/package-info.java @@ -0,0 +1,7 @@ +/** + * Support for MockMvc and HtmlUnitDriver + * + * @author Rob Winch + * @since 4.2 + */ +package org.springframework.test.web.servlet.htmlunit.webdriver; \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java new file mode 100644 index 00000000000..98679a05113 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java @@ -0,0 +1,137 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import java.net.URL; +import java.util.Collections; + +import com.gargoylesoftware.htmlunit.HttpWebConnection; +import com.gargoylesoftware.htmlunit.Page; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebConnection; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.WebResponseData; +import com.gargoylesoftware.htmlunit.util.NameValuePair; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.Matchers.isEmptyString; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertThat; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import static org.mockito.Mockito.*; +import org.mockito.runners.MockitoJUnitRunner; +import static org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection.DelegateWebConnection; + +import org.springframework.stereotype.Controller; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +/** + * @author Rob Winch + */ +@RunWith(MockitoJUnitRunner.class) +public class DelegatingWebConnectionTests { + private DelegatingWebConnection webConnection; + + @Mock + private WebRequestMatcher matcher1; + @Mock + private WebRequestMatcher matcher2; + @Mock + private WebConnection defaultConnection; + @Mock + private WebConnection connection1; + @Mock + private WebConnection connection2; + + private WebRequest request; + private WebResponse expectedResponse; + + @Before + public void setUp() throws Exception { + request = new WebRequest(new URL("http://localhost/")); + WebResponseData data = new WebResponseData("".getBytes("UTF-8"),200, "", Collections.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 {} +} \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/ForwardController.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/ForwardController.java new file mode 100644 index 00000000000..56dba457610 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/ForwardController.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * @author Rob Winch + */ + @Controller +public class ForwardController { + + @RequestMapping("/forward") + public String forward() { + return "forward:/"; + } +} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HelloController.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HelloController.java new file mode 100644 index 00000000000..95c0f805728 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HelloController.java @@ -0,0 +1,34 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * @author Rob Winch + */ +@Controller +public class HelloController { + @RequestMapping + @ResponseBody + public String header(HttpServletRequest request) { + return "hello"; + } +} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcherTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcherTests.java new file mode 100644 index 00000000000..12691146905 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcherTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import java.net.URL; + +import com.gargoylesoftware.htmlunit.WebRequest; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import org.junit.Test; + +import org.springframework.test.web.servlet.htmlunit.HostRequestMatcher; +import org.springframework.test.web.servlet.htmlunit.WebRequestMatcher; + +/** + * @author Rob Winch + */ +public class HostRequestMatcherTests { + + @Test + public void localhostMatches() throws Exception { + WebRequestMatcher matcher = new HostRequestMatcher("localhost"); + + boolean matches = matcher.matches(new WebRequest(new URL("http://localhost/jquery-1.11.0.min.js"))); + assertThat(matches, equalTo(true));; + + matches = matcher.matches(new WebRequest(new URL("http://example.com/jquery-1.11.0.min.js"))); + assertThat(matches, equalTo(false));; + } + + @Test + public void multipleHosts() throws Exception { + WebRequestMatcher matcher = new HostRequestMatcher("localhost","example.com"); + + boolean matches = matcher.matches(new WebRequest(new URL("http://localhost/jquery-1.11.0.min.js"))); + assertThat(matches, equalTo(true));; + + matches = matcher.matches(new WebRequest(new URL("http://example.com/jquery-1.11.0.min.js"))); + assertThat(matches, equalTo(true));; + } + + @Test + public void specificPort() throws Exception { + WebRequestMatcher matcher = new HostRequestMatcher("localhost:8080"); + + boolean matches = matcher.matches(new WebRequest(new URL("http://localhost:8080/jquery-1.11.0.min.js"))); + assertThat(matches, equalTo(true));; + + matches = matcher.matches(new WebRequest(new URL("http://localhost:9090/jquery-1.11.0.min.js"))); + assertThat(matches, equalTo(false));; + } + + @Test + public void defaultPortInMatcher() throws Exception { + WebRequestMatcher matcher = new HostRequestMatcher("localhost:80"); + + boolean matches = matcher.matches(new WebRequest(new URL("http://localhost:80/jquery-1.11.0.min.js"))); + assertThat(matches, equalTo(true));; + + matches = matcher.matches(new WebRequest(new URL("http://localhost/jquery-1.11.0.min.js"))); + assertThat(matches, equalTo(true));; + + matches = matcher.matches(new WebRequest(new URL("http://localhost:9090/jquery-1.11.0.min.js"))); + assertThat(matches, equalTo(false));; + } +} \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java new file mode 100644 index 00000000000..5572fa3f63a --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java @@ -0,0 +1,782 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import javax.servlet.ServletContext; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpSession; + +import com.gargoylesoftware.htmlunit.CookieManager; +import com.gargoylesoftware.htmlunit.HttpMethod; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.util.NameValuePair; +import org.apache.commons.io.IOUtils; +import org.apache.http.auth.UsernamePasswordCredentials; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; +import org.junit.Before; +import org.junit.Test; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +/** + * + * @author Rob Winch + * + */ +public class HtmlUnitRequestBuilderTests { + private WebRequest webRequest; + + private ServletContext servletContext; + + private Map 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 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¶m2=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"); + } +} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java new file mode 100644 index 00000000000..a6343cca24d --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java @@ -0,0 +1,155 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.springframework.test.web.servlet.htmlunit; + +import java.io.IOException; +import java.net.URL; +import javax.servlet.http.HttpServletRequest; + +import com.gargoylesoftware.htmlunit.WebConnection; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertThat; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.Mockito.mock; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * @author Rob Winch + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +public class MockMvcConnectionBuilderSupportTests { + @Autowired + WebApplicationContext context; + + MockMvc mockMvc; + + WebConnection delegateConnection; + + WebConnection connection; + + @Before + public void setup() { + delegateConnection = mock(WebConnection.class); + mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + + connection = new MockMvcWebConnectionBuilderSupport(mockMvc){} + .createConnection(delegateConnection); + } + + @Test(expected = IllegalArgumentException.class) + public void constructorMockMvcNull() { + new MockMvcWebConnectionBuilderSupport((MockMvc)null){}; + } + + @Test(expected = IllegalArgumentException.class) + public void constructorContextNull() { + new MockMvcWebConnectionBuilderSupport((WebApplicationContext)null){}; + } + + @Test + public void context() throws Exception { + connection = new MockMvcWebConnectionBuilderSupport(context){} + .createConnection(delegateConnection); + + assertMvcProcessed("http://localhost/"); + assertDelegateProcessed("http://example.com/"); + } + + @Test + public void mockMvc() throws Exception { + assertMvcProcessed("http://localhost/"); + assertDelegateProcessed("http://example.com/"); + } + + @Test + public void mockMvcExampleDotCom() throws Exception { + connection = new MockMvcWebConnectionBuilderSupport(context){} + .useMockMvcForHosts("example.com") + .createConnection(delegateConnection); + + assertMvcProcessed("http://localhost/"); + assertMvcProcessed("http://example.com/"); + assertDelegateProcessed("http://other.com/"); + } + + @Test + public void mockMvcAlwaysUseMockMvc() throws Exception { + connection = new MockMvcWebConnectionBuilderSupport(context){} + .alwaysUseMockMvc() + .createConnection(delegateConnection); + + assertMvcProcessed("http://other.com/"); + } + + @Test + public void defaultContextPathEmpty() throws Exception { + connection = new MockMvcWebConnectionBuilderSupport(context){} + .createConnection(delegateConnection); + + assertThat(getWebResponse("http://localhost/abc").getContentAsString(), equalTo(""));; + } + + @Test + public void defaultContextPathCustom() throws Exception { + connection = new MockMvcWebConnectionBuilderSupport(context) { + }.contextPath("/abc").createConnection(delegateConnection); + + assertThat(getWebResponse("http://localhost/abc/def").getContentAsString(), equalTo("/abc"));; + } + + private void assertMvcProcessed(String url) throws Exception { + assertThat(getWebResponse(url), notNullValue()); + } + + private void assertDelegateProcessed(String url) throws Exception { + assertThat(getWebResponse(url), nullValue()); + } + + private WebResponse getWebResponse(String url) throws IOException { + return connection.getResponse(new WebRequest(new URL(url))); + } + + @Configuration + @EnableWebMvc + static class Config { + @RestController + static class ContextPathController { + @RequestMapping + public String contextPath(HttpServletRequest request) { + return request.getContextPath(); + } + } + } +} \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java new file mode 100644 index 00000000000..c569052ca21 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java @@ -0,0 +1,117 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + package org.springframework.test.web.servlet.htmlunit; + +import java.io.IOException; +import java.net.URL; +import javax.servlet.http.HttpServletRequest; + +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertThat; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * @author Rob Winch + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +public class MockMvcWebClientBuilderTests { + @Autowired + WebApplicationContext context; + + MockMvc mockMvc; + + WebClient webClient; + + @Before + public void setup() { + mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + webClient = new WebClient(); + } + + @Test(expected = IllegalArgumentException.class) + public void mockMvcSetupNull() { + MockMvcWebClientBuilder.mockMvcSetup(null); + } + + @Test(expected = IllegalArgumentException.class) + public void webAppContextSetupNull() { + MockMvcWebClientBuilder.webAppContextSetup(null); + } + + @Test + public void mockMvcSetupconfigureWebClient() throws Exception { + webClient = MockMvcWebClientBuilder + .mockMvcSetup(mockMvc) + .configureWebClient(webClient); + + assertMvcProcessed("http://localhost/test"); + assertDelegateProcessed("http://example.com/"); + } + + @Test + public void mockMvcSetupCreateWebClient() throws Exception { + webClient = MockMvcWebClientBuilder + .mockMvcSetup(mockMvc) + .createWebClient(); + + assertMvcProcessed("http://localhost/test"); + assertDelegateProcessed("http://example.com/"); + } + + private void assertMvcProcessed(String url) throws Exception { + assertThat(getWebResponse(url).getContentAsString(), equalTo("mvc"));; + } + + private void assertDelegateProcessed(String url) throws Exception { + assertThat(getWebResponse(url).getContentAsString(), not(equalTo("mvc"))); + } + + private WebResponse getWebResponse(String url) throws IOException { + return webClient.getWebConnection().getResponse(new WebRequest(new URL(url))); + } + + @Configuration + @EnableWebMvc + static class Config { + @RestController + static class ContextPathController { + @RequestMapping + public String contextPath(HttpServletRequest request) { + return "mvc"; + } + } + } +} \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java new file mode 100644 index 00000000000..04344cf0a1f --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import java.io.IOException; + +import com.gargoylesoftware.htmlunit.Page; +import com.gargoylesoftware.htmlunit.WebClient; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +/** + * @author Rob Winch + */ +public class MockMvcWebConnectionTests { + MockMvc mockMvc; + + WebClient webClient; + + @Before + public void setup() { + mockMvc = MockMvcBuilders + .standaloneSetup(new HelloController(), new ForwardController()) + .build(); + + webClient = new WebClient(); + } + + @Test + public void contextPathNull() throws IOException { + webClient.setWebConnection(new MockMvcWebConnection(mockMvc, null)); + + Page page = webClient.getPage("http://localhost/context/a"); + + assertThat(page.getWebResponse().getStatusCode(), equalTo(200));; + } + + @Test + public void contextPathExplicit() throws IOException { + webClient.setWebConnection(new MockMvcWebConnection(mockMvc, "/context")); + + Page page = webClient.getPage("http://localhost/context/a"); + + assertThat(page.getWebResponse().getStatusCode(), equalTo(200));; + } + + @Test + public void contextPathEmpty() throws IOException { + webClient.setWebConnection(new MockMvcWebConnection(mockMvc, "")); + + Page page = webClient.getPage("http://localhost/context/a"); + + assertThat(page.getWebResponse().getStatusCode(), equalTo(200));; + } + + @Test + public void forward() throws IOException { + webClient.setWebConnection(new MockMvcWebConnection(mockMvc, "")); + + Page page = webClient.getPage("http://localhost/forward"); + + assertThat(page.getWebResponse().getContentAsString(), equalTo("hello"));; + } + + @Test(expected = IllegalArgumentException.class) + public void contextPathDoesNotStartWithSlash() throws IOException { + new MockMvcWebConnection(mockMvc, "context"); + } + + @Test(expected = IllegalArgumentException.class) + public void contextPathEndsWithSlash() throws IOException { + new MockMvcWebConnection(mockMvc, "/context/"); + } +} \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java new file mode 100644 index 00000000000..b4134894a87 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import java.net.URL; +import java.util.List; + +import com.gargoylesoftware.htmlunit.WebRequest; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.util.NameValuePair; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import org.junit.Before; +import org.junit.Test; + +import org.springframework.mock.web.MockHttpServletResponse; + +/** + * + * @author Rob Winch + */ +public class MockWebResponseBuilderTests { + + private WebRequest webRequest; + + private MockHttpServletResponse httpServletResponse; + + private MockWebResponseBuilder responseBuilder; + + @Before + public void setUp() throws Exception { + webRequest = new WebRequest(new URL("http://example.com:80/test/this/here")); + httpServletResponse = new MockHttpServletResponse(); + + responseBuilder = new MockWebResponseBuilder(System.currentTimeMillis(), webRequest, httpServletResponse); + } + + // --- constructor + + @Test(expected = IllegalArgumentException.class) + public void constructorNullWebRequest() { + new MockWebResponseBuilder(0L, null, httpServletResponse); + } + + @Test(expected = IllegalArgumentException.class) + public void constructorNullResponse() throws Exception { + new MockWebResponseBuilder(0L, new WebRequest(new URL("http://example.com:80/test/this/here")), null); + } + + // --- build + + @Test + public void buildContent() throws Exception { + httpServletResponse.getWriter().write("expected content"); + + WebResponse webResponse = responseBuilder.build(); + + assertThat(webResponse.getContentAsString(), equalTo("expected content"));; + } + + @Test + public void buildContentCharset() throws Exception { + httpServletResponse.addHeader("Content-Type", "text/html; charset=UTF-8"); + WebResponse webResponse = responseBuilder.build(); + + assertThat(webResponse.getContentCharset(), equalTo("UTF-8"));; + } + + @Test + public void buildContentType() throws Exception { + httpServletResponse.addHeader("Content-Type", "text/html; charset-UTF-8"); + WebResponse webResponse = responseBuilder.build(); + + assertThat(webResponse.getContentType(), equalTo("text/html"));; + } + + @Test + public void buildResponseHeaders() throws Exception { + httpServletResponse.addHeader("Content-Type", "text/html"); + httpServletResponse.addHeader("X-Test", "value"); + WebResponse webResponse = responseBuilder.build(); + + List 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));; + } +} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcherTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcherTests.java new file mode 100644 index 00000000000..40ce0056143 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcherTests.java @@ -0,0 +1,43 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit; + +import java.net.URL; + +import com.gargoylesoftware.htmlunit.WebRequest; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import org.junit.Test; + +import org.springframework.test.web.servlet.htmlunit.UrlRegexRequestMatcher; +import org.springframework.test.web.servlet.htmlunit.WebRequestMatcher; + +/** + * @author Rob Winch + */ +public class UrlRegexRequestMatcherTests { + + @Test + public void classlevelJavadoc() throws Exception { + WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*"); + + boolean matches = cdnMatcher.matches(new WebRequest(new URL("http://code.jquery.com/jquery-1.11.0.min.js"))); + assertThat(matches, equalTo(true)); + + matches = cdnMatcher.matches(new WebRequest(new URL("http://localhost/jquery-1.11.0.min.js"))); + assertThat(matches, equalTo(false)); + } +} \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java new file mode 100644 index 00000000000..871273b134c --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java @@ -0,0 +1,136 @@ +/* + * Copyright 2002-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.springframework.test.web.servlet.htmlunit.webdriver; + +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; + +import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.openqa.selenium.htmlunit.HtmlUnitDriver; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +/** + * @author Rob Winch + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration +@WebAppConfiguration +public class MockMvcHtmlUnitDriverBuilderTests { + + public static final String EXPECTED_BODY = "MockMvcHtmlUnitDriverBuilderTests mvc"; + + @Autowired + WebApplicationContext context; + + MockMvc mockMvc; + + HtmlUnitDriver driver; + + @Before + public void setup() { + mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void mockMvcSetupNull() { + MockMvcHtmlUnitDriverBuilder.mockMvcSetup(null); + } + + @Test(expected = IllegalArgumentException.class) + public void webAppContextSetupNull() { + MockMvcHtmlUnitDriverBuilder.webAppContextSetup(null); + } + + @Test + public void mockMvcSetupConfigureDriver() throws Exception { + driver = MockMvcHtmlUnitDriverBuilder + .mockMvcSetup(mockMvc) + .configureDriver(new WebConnectionHtmlUnitDriver()); + + assertMvcProcessed("http://localhost/test"); + assertDelegateProcessed("http://example.com/"); + } + + @Test + public void mockMvcSetupCreateDriver() throws Exception { + driver = MockMvcHtmlUnitDriverBuilder + .mockMvcSetup(mockMvc) + .createDriver(); + + assertMvcProcessed("http://localhost/test"); + assertDelegateProcessed("http://example.com/"); + } + + @Test + public void javascriptEnabledDefaultEnabled() { + driver = MockMvcHtmlUnitDriverBuilder + .mockMvcSetup(mockMvc) + .createDriver(); + + assertThat(driver.isJavascriptEnabled(), equalTo(true)); + } + + @Test + public void javascriptEnabledDisabled() { + driver = MockMvcHtmlUnitDriverBuilder + .mockMvcSetup(mockMvc) + .javascriptEnabled(false) + .createDriver(); + + assertThat(driver.isJavascriptEnabled(), equalTo(false)); + } + + private void assertMvcProcessed(String url) throws Exception { + assertThat(get(url), containsString(EXPECTED_BODY)); + } + + private void assertDelegateProcessed(String url) throws Exception { + assertThat(get(url), not(containsString(EXPECTED_BODY))); + } + + private String get(String url) throws IOException { + driver.get(url); + return driver.getPageSource(); + } + + @Configuration + @EnableWebMvc + static class Config { + @RestController + static class ContextPathController { + @RequestMapping + public String contextPath(HttpServletRequest request) { + return EXPECTED_BODY; + } + } + } + +} \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriverTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriverTests.java new file mode 100644 index 00000000000..afcdf185c7d --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriverTests.java @@ -0,0 +1,55 @@ +package org.springframework.test.web.servlet.htmlunit.webdriver; + +import com.gargoylesoftware.htmlunit.WebConnection; +import com.gargoylesoftware.htmlunit.WebRequest; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.Matchers.any; +import org.mockito.Mock; +import static org.mockito.Mockito.when; +import org.mockito.runners.MockitoJUnitRunner; + +/** + * @author Rob Winch + */ +// tag::junit-spring-setup[] +@RunWith(MockitoJUnitRunner.class) +public class WebConnectionHtmlUnitDriverTests { + @Mock + WebConnection connection; + + WebConnectionHtmlUnitDriver driver; + + @Before + public void setup() throws Exception { + driver = new WebConnectionHtmlUnitDriver(); + + when(connection.getResponse(any(WebRequest.class))).thenThrow(new InternalError("")); + } + + @Test + public void getWebConnectionDefaultNotNull() { + assertThat(driver.getWebConnection(), notNullValue()); + } + + @Test + public void setWebConnection() { + driver.setWebConnection(connection); + + assertThat(driver.getWebConnection(), equalTo(connection)); + try { + driver.get("https://example.com"); + fail("Expected Exception"); + } catch (InternalError success) {} + } + + @Test(expected = IllegalArgumentException.class) + public void setWebConnectionNull() { + driver.setWebConnection(null); + } +} \ No newline at end of file diff --git a/src/asciidoc/testing.adoc b/src/asciidoc/testing.adoc index 12dadb237d3..745432ac7fa 100644 --- a/src/asciidoc/testing.adoc +++ b/src/asciidoc/testing.adoc @@ -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 <> 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] +---- +
+ + + + + + + + +
+ +
+
+---- + +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: + +* <> - Use this option if you want the raw libraries +* <> - Use this option to ease development and be able to reuse code +between integration and end-to-end testing. +* <> - 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 +<> +==== + +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 <>. +==== + +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 <> 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-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 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 <> +==== + +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 +<> by leveraging the Page Object +Pattern. As we mentioned in <>, 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 createMessage(Class 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)] +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-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 +<> 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 <> +==== + +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 +<>. The most obvious change is +that we are now using the Page Object Pattern. As we mentioned in +<>, 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 From bc1f0d3bdce5c3f54c02557a6c03712df33810eb Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 23 Jul 2015 19:09:29 +0200 Subject: [PATCH 2/5] Mention HtmlUnit support in the What's New section Issue: SPR-13158 --- src/asciidoc/whats-new.adoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/asciidoc/whats-new.adoc b/src/asciidoc/whats-new.adoc index 16ec877b444..5e37ce26ba2 100644 --- a/src/asciidoc/whats-new.adoc +++ b/src/asciidoc/whats-new.adoc @@ -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 <> 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. From 7d16e3b97c71e18b98ca27fa2635af00bef0c9f6 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 23 Jul 2015 14:39:21 +0200 Subject: [PATCH 3/5] Upgrade to HtmlUnit 2.18 GA Issue: SPR-13158 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 7459e7cb76f..6537b840051 100644 --- a/build.gradle +++ b/build.gradle @@ -44,7 +44,7 @@ configure(allprojects) { project -> ext.httpclientVersion = "4.5" ext.httpasyncVersion = "4.1" ext.jackson2Version = "2.6.0" - ext.htmlunitVersion = "2.17" + ext.htmlunitVersion = "2.18" ext.jasperreportsVersion = "6.1.0" ext.javamailVersion = "1.5.4" ext.jettyVersion = "9.3.1.v20150714" From 8ff247b17a1642996da5c1f066a1bd9f8ef01f24 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 23 Jul 2015 19:37:29 +0200 Subject: [PATCH 4/5] Ensure spring-test builds against HttpComponents 4.5 This is necessary to ensure that the version of HtmlUnit that we use is compatible with HttpComponents 4.5. Issue: SPR-13158 --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 6537b840051..216662d3f2c 100644 --- a/build.gradle +++ b/build.gradle @@ -1032,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) { From d21ad2998285ffbd0414c3b928cd6f864780486a Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Thu, 23 Jul 2015 17:45:03 +0200 Subject: [PATCH 5/5] Polish MockMvc HtmlUnit Support - formatting - code style - organized imports - precondition assertions - suppressed warnings - Javadoc enhancements Issue: SPR-13158 --- .../htmlunit/DelegatingWebConnection.java | 42 +++-- .../htmlunit/ForwardRequestPostProcessor.java | 15 +- .../servlet/htmlunit/HostRequestMatcher.java | 61 +++--- .../htmlunit/HtmlUnitRequestBuilder.java | 177 +++++++++--------- .../htmlunit/MockMvcWebClientBuilder.java | 59 +++--- .../htmlunit/MockMvcWebConnection.java | 94 +++++----- .../MockMvcWebConnectionBuilderSupport.java | 111 ++++++----- .../htmlunit/MockWebResponseBuilder.java | 49 +++-- .../htmlunit/UrlRegexRequestMatcher.java | 23 ++- .../servlet/htmlunit/WebRequestMatcher.java | 16 +- .../web/servlet/htmlunit/package-info.java | 7 +- .../MockMvcHtmlUnitDriverBuilder.java | 59 +++--- .../WebConnectionHtmlUnitDriver.java | 72 ++++--- .../htmlunit/webdriver/package-info.java | 8 +- .../DelegatingWebConnectionTests.java | 32 ++-- .../servlet/htmlunit/ForwardController.java | 7 +- .../web/servlet/htmlunit/HelloController.java | 6 +- .../htmlunit/HostRequestMatcherTests.java | 14 +- .../htmlunit/HtmlUnitRequestBuilderTests.java | 25 +-- .../MockMvcConnectionBuilderSupportTests.java | 29 +-- .../MockMvcWebClientBuilderTests.java | 24 ++- .../htmlunit/MockMvcWebConnectionTests.java | 19 +- .../htmlunit/MockWebResponseBuilderTests.java | 15 +- .../htmlunit/UrlRegexRequestMatcherTests.java | 14 +- .../MockMvcHtmlUnitDriverBuilderTests.java | 4 +- .../WebConnectionHtmlUnitDriverTests.java | 33 +++- 26 files changed, 573 insertions(+), 442 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java index cd03c96786c..1fc2e176709 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnection.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,23 +13,27 @@ * 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; -import org.springframework.util.Assert; - /** - *

- * Implementation of WebConnection that allows delegating to various WebConnection implementations. For example, if - * you host your JavaScript on the domain code.jquery.com, you might want to use the following:

- *
+ * Implementation of {@link WebConnection} that allows delegating to various
+ * {@code WebConnection} implementations.
+ *
+ * 

For example, if you host your JavaScript on the domain {@code code.jquery.com}, + * you might want to use the following. + * + *

  * WebClient webClient = new WebClient();
  *
  * MockMvc mockMvc = ...
@@ -45,38 +49,47 @@ import org.springframework.util.Assert;
  * WebClient webClient = new WebClient();
  * webClient.setWebConnection(webConnection);
  * 
+ * * @author Rob Winch + * @author Sam Brannen * @since 4.2 */ public final class DelegatingWebConnection implements WebConnection { + private final List connections; + private final WebConnection defaultConnection; + public DelegatingWebConnection(WebConnection defaultConnection, List connections) { - Assert.notNull(defaultConnection, "defaultConnection cannot be null"); - Assert.notEmpty(connections, "connections cannot be empty"); + 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) { + public DelegatingWebConnection(WebConnection defaultConnection, DelegateWebConnection... connections) { this(defaultConnection, Arrays.asList(connections)); } @Override public WebResponse getResponse(WebRequest request) throws IOException { - for(DelegateWebConnection connection : connections) { - if(connection.getMatcher().matches(request)) { + for (DelegateWebConnection connection : this.connections) { + if (connection.getMatcher().matches(request)) { return connection.getDelegate().getResponse(request); } } - return defaultConnection.getResponse(request); + return this.defaultConnection.getResponse(request); } - public final static class DelegateWebConnection { + + 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; @@ -90,4 +103,5 @@ public final class DelegatingWebConnection implements WebConnection { return delegate; } } + } \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/ForwardRequestPostProcessor.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/ForwardRequestPostProcessor.java index 93a5f5d75cf..75284bf4475 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/ForwardRequestPostProcessor.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/ForwardRequestPostProcessor.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,6 +13,7 @@ * 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; @@ -21,19 +22,23 @@ 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 url) { - Assert.hasText(url, "Forward url must have text"); - forwardUrl = url; + + 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(forwardUrl); + request.setServletPath(this.forwardUrl); return request; } + } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcher.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcher.java index 98995550385..e6cee1e665a 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcher.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcher.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.springframework.test.web.servlet.htmlunit; import java.net.URL; @@ -23,50 +24,45 @@ import java.util.Set; import com.gargoylesoftware.htmlunit.WebRequest; /** - *

- * An implementation of WebRequestMatcher that allows matching on the host and optionally - * the port of WebRequest#getUrl(). For example, the following would match any request to - * the host "code.jquery.com" without regard for the port: - *

+ * A {@link WebRequestMatcher} that allows matching on the host and optionally + * the port of {@code WebRequest#getUrl()}. + * + *

For example, the following would match any request to the host + * {@code "code.jquery.com"} without regard for the port. * - *

- * WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com");
- * 
+ *
WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com");
* - * Multiple hosts can also be passed in. For example, the following would match an request - * to the host "code.jquery.com" or the host "cdn.com" without regard for the port: + *

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. * - *

- * WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com", "cdn.com");
- * 
+ *
WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com", "cdn.com");
* - *

- * Alternatively, one can also specify the port. For example, the following would match - * any request to the host "code.jquery.com" with the port of 80. - *

+ *

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}. * - *

- * WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com:80");
- * 
+ *
WebRequestMatcher cdnMatcher = new HostMatcher("code.jquery.com:80");
* - *

- * The above cdnMatcher would match: "http://code.jquery.com/jquery.js" (default port of - * 80) and "http://code.jquery.com:80/jquery.js". However, it would not match - * "https://code.jquery.com/jquery.js" (default port of 443). - *

+ *

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 hosts = new HashSet(); + /** - * Creates a new instance - * - * @param hosts the hosts to match on (i.e. "localhost", "example.com:443") + * Create a new {@code HostRequestMatcher} for the given hosts — + * 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)); @@ -77,16 +73,17 @@ public final class HostRequestMatcher implements WebRequestMatcher { URL url = request.getUrl(); String host = url.getHost(); - if(hosts.contains(host)) { + if (this.hosts.contains(host)) { return true; } int port = url.getPort(); - if(port == -1) { + if (port == -1) { port = url.getDefaultPort(); } String hostAndPort = host + ":" + port; - return hosts.contains(hostAndPort); + return this.hosts.contains(hostAndPort); } + } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java index e8e48af01f7..d9ef067c6ce 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilder.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.springframework.test.web.servlet.htmlunit; import java.io.UnsupportedEncodingException; @@ -33,11 +34,6 @@ import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; -import com.gargoylesoftware.htmlunit.CookieManager; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.WebRequest; -import com.gargoylesoftware.htmlunit.util.NameValuePair; - import org.springframework.beans.Mergeable; import org.springframework.http.MediaType; import org.springframework.mock.web.MockHttpServletRequest; @@ -49,21 +45,27 @@ 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 allow a {@link WebRequest} into a {@link MockHttpServletRequest} using Spring MVC Test's - * {@link RequestBuilder}. - *

- *

- * By default the first path segment of the URL is used as the contextPath. To override this default see - * {@link #setContextPath(String)}. - *

+ * Internal class used to transform a {@link WebRequest} into a + * {@link MockHttpServletRequest} using Spring MVC Test's {@link RequestBuilder}. + * + *

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 sessions; private final WebClient webClient; @@ -78,18 +80,19 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { private RequestPostProcessor forwardPostProcessor; + /** - * - * @param sessions A {@link Map} of the {@link HttpSession#getId()} to currently managed {@link HttpSession} - * objects. Cannot be null. + * 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}. Cannot be null. + * @param webRequest the {@link WebRequest} to transform into a + * {@link MockHttpServletRequest}; never {@code null} */ - public HtmlUnitRequestBuilder(Map sessions, WebClient webClient, - WebRequest webRequest) { - Assert.notNull(sessions, "sessions cannot be null"); - Assert.notNull(webClient, "webClient cannot be null"); - Assert.notNull(webRequest, "webRequest cannot be null"); + public HtmlUnitRequestBuilder(Map 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; @@ -98,12 +101,12 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { public MockHttpServletRequest buildRequest(ServletContext servletContext) { String charset = getCharset(); - String httpMethod = webRequest.getHttpMethod().name(); + String httpMethod = this.webRequest.getHttpMethod().name(); UriComponents uriComponents = uriComponents(); MockHttpServletRequest result = new HtmlUnitMockHttpServletRequest(servletContext, httpMethod, uriComponents.getPath()); - parent(result, parentBuilder); + parent(result, this.parentBuilder); result.setServerName(uriComponents.getHost()); // needs to be first for additional headers authType(result); result.setCharacterEncoding(charset); @@ -125,27 +128,27 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { } private MockHttpServletRequest postProcess(MockHttpServletRequest request) { - if(parentPostProcessor != null) { - request = parentPostProcessor.postProcessRequest(request); + if (this.parentPostProcessor != null) { + request = this.parentPostProcessor.postProcessRequest(request); } - if(forwardPostProcessor != null) { - request = forwardPostProcessor.postProcessRequest(request); + if (this.forwardPostProcessor != null) { + request = this.forwardPostProcessor.postProcessRequest(request); } - return request; } private void parent(MockHttpServletRequest result, RequestBuilder parent) { - if(parent == null) { + if (parent == null) { return; } + MockHttpServletRequest parentRequest = parent.buildRequest(result.getServletContext()); // session HttpSession parentSession = parentRequest.getSession(false); - if(parentSession != null) { + if (parentSession != null) { Enumeration attrNames = parentSession.getAttributeNames(); - while(attrNames.hasMoreElements()) { + while (attrNames.hasMoreElements()) { String attrName = attrNames.nextElement(); Object attrValue = parentSession.getAttribute(attrName); result.getSession().setAttribute(attrName, attrValue); @@ -154,10 +157,10 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { // header Enumeration headerNames = parentRequest.getHeaderNames(); - while(headerNames.hasMoreElements()) { + while (headerNames.hasMoreElements()) { String attrName = headerNames.nextElement(); Enumeration attrValues = parentRequest.getHeaders(attrName); - while(attrValues.hasMoreElements()) { + while (attrValues.hasMoreElements()) { String attrValue = attrValues.nextElement(); result.addHeader(attrName, attrValue); } @@ -165,7 +168,7 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { // parameter Map parentParams = parentRequest.getParameterMap(); - for(Map.Entry parentParam : parentParams.entrySet()) { + for (Map.Entry parentParam : parentParams.entrySet()) { String paramName = parentParam.getKey(); String[] paramValues = parentParam.getValue(); result.addParameter(paramName, paramValues); @@ -173,42 +176,34 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { // cookie Cookie[] parentCookies = parentRequest.getCookies(); - if(parentCookies != null) { + if (parentCookies != null) { result.setCookies(parentCookies); } // request attribute Enumeration parentAttrNames = parentRequest.getAttributeNames(); - while(parentAttrNames.hasMoreElements()) { + while (parentAttrNames.hasMoreElements()) { String parentAttrName = parentAttrNames.nextElement(); result.setAttribute(parentAttrName, parentRequest.getAttribute(parentAttrName)); } } /** - * Sets the contextPath to be used. The value may be null in which case the first path segment of the URL is turned - * into the contextPath. Otherwise it must conform to {@link HttpServletRequest#getContextPath()} which states it - * can be empty string or it must start with a "/" and not end in a "/". - * - * @param contextPath A valid contextPath - * @throws IllegalArgumentException if contextPath is not a valid {@link HttpServletRequest#getContextPath()}. + * Set the contextPath to be used. + *

The value may be null in which case the first path segment of the + * URL is turned into the contextPath. Otherwise it must conform to + * {@link HttpServletRequest#getContextPath()} which states it can be + * 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) { - if (contextPath == null || "".equals(contextPath)) { - this.contextPath = contextPath; - return; - } - if (contextPath.endsWith("/")) { - throw new IllegalArgumentException("contextPath cannot end with /. Got '" + contextPath + "'"); - } - if (!contextPath.startsWith("/")) { - throw new IllegalArgumentException("contextPath must start with /. Got '" + contextPath + "'"); - } + MockMvcWebConnection.validateContextPath(contextPath); this.contextPath = contextPath; } - public void setForwardPostProcessor(RequestPostProcessor postProcessor) { - this.forwardPostProcessor = postProcessor; + public void setForwardPostProcessor(RequestPostProcessor forwardPostProcessor) { + this.forwardPostProcessor = forwardPostProcessor; } private void authType(MockHttpServletRequest request) { @@ -220,7 +215,7 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { } private void content(MockHttpServletRequest result, String charset) { - String requestBody = webRequest.getRequestBody(); + String requestBody = this.webRequest.getRequestBody(); if (requestBody == null) { return; } @@ -238,7 +233,7 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { } private void contextPath(MockHttpServletRequest result, UriComponents uriComponents) { - if (contextPath == null) { + if (this.contextPath == null) { List pathSegments = uriComponents.getPathSegments(); if (pathSegments.isEmpty()) { result.setContextPath(""); @@ -248,11 +243,11 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { } } else { - if (!uriComponents.getPath().startsWith(contextPath)) { + if (!uriComponents.getPath().startsWith(this.contextPath)) { throw new IllegalArgumentException(uriComponents.getPath() + " should start with contextPath " - + contextPath); + + this.contextPath); } - result.setContextPath(contextPath); + result.setContextPath(this.contextPath); } } @@ -265,21 +260,20 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { while (tokens.hasMoreTokens()) { String cookieName = tokens.nextToken().trim(); if (!tokens.hasMoreTokens()) { - throw new IllegalArgumentException("Expected value for cookie name " + cookieName - + ". Full cookie was " + cookieHeaderValue); + throw new IllegalArgumentException("Expected value for cookie name '" + cookieName + + "'. Full cookie was " + cookieHeaderValue); } String cookieValue = tokens.nextToken().trim(); processCookie(result, cookies, new Cookie(cookieName, cookieValue)); } } - @SuppressWarnings("unchecked") - Set managedCookies = webClient.getCookies(webRequest.getUrl()); + Set 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) { + if (parentCookies != null) { + for (Cookie cookie : parentCookies) { cookies.add(cookie); } } @@ -297,7 +291,7 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { } private String getCharset() { - String charset = webRequest.getCharset(); + String charset = this.webRequest.getCharset(); if (charset == null) { return "ISO-8859-1"; } @@ -305,24 +299,24 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { } private String header(String headerName) { - return webRequest.getAdditionalHeaders().get(headerName); + return this.webRequest.getAdditionalHeaders().get(headerName); } private void headers(MockHttpServletRequest result) { - for (Entry header : webRequest.getAdditionalHeaders().entrySet()) { + for (Entry header : this.webRequest.getAdditionalHeaders().entrySet()) { result.addHeader(header.getKey(), header.getValue()); } } private MockHttpSession httpSession(MockHttpServletRequest request, final String sessionid) { MockHttpSession session; - synchronized (sessions) { - session = sessions.get(sessionid); + synchronized (this.sessions) { + session = this.sessions.get(sessionid); if (session == null) { session = new HtmlUnitMockHttpSession(request, sessionid); session.setNew(true); - synchronized (sessions) { - sessions.put(sessionid, session); + synchronized (this.sessions) { + this.sessions.put(sessionid, session); } addSessionCookie(request, sessionid); } @@ -371,7 +365,7 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { } } } - for (NameValuePair param : webRequest.getRequestParameters()) { + for (NameValuePair param : this.webRequest.getRequestParameters()) { result.addParameter(param.getName(), param.getValue()); } } @@ -416,7 +410,7 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { int serverPort = uriComponents.getPort(); result.setServerPort(serverPort); if (serverPort == -1) { - int portConnection = webRequest.getUrl().getDefaultPort(); + int portConnection = this.webRequest.getUrl().getDefaultPort(); result.setLocalPort(serverPort); result.setRemotePort(portConnection); } @@ -426,7 +420,7 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { } private UriComponents uriComponents() { - URL url = webRequest.getUrl(); + URL url = this.webRequest.getUrl(); UriComponentsBuilder uriBldr = UriComponentsBuilder.fromUriString(url.toExternalForm()); return uriBldr.build(); } @@ -441,7 +435,7 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { if (parent == null) { return this; } - if(parent instanceof RequestBuilder) { + if (parent instanceof RequestBuilder) { this.parentBuilder = (RequestBuilder) parent; } if (parent instanceof SmartRequestBuilder) { @@ -451,13 +445,13 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { return this; } + /** - * An extension to {@link MockHttpServletRequest} that ensures that when a new {@link HttpSession} is created, it is - * added to the managed sessions. - * - * @author Rob Winch + * 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); } @@ -469,8 +463,8 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { setSession(newSession); newSession.setNew(true); String sessionid = newSession.getId(); - synchronized (sessions) { - sessions.put(sessionid, newSession); + synchronized (HtmlUnitRequestBuilder.this.sessions) { + HtmlUnitRequestBuilder.this.sessions.put(sessionid, newSession); } addSessionCookie(this, sessionid); result = newSession; @@ -488,12 +482,12 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { } /** - * An extension to {@link MockHttpSession} that ensures when {@link #invalidate()} is called that the - * {@link HttpSession} is removed from the managed sessions. - * - * @author Rob Winch + * 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) { @@ -508,16 +502,15 @@ final class HtmlUnitRequestBuilder implements RequestBuilder, Mergeable { public void invalidate() { super.invalidate(); - synchronized (sessions) { - sessions.remove(getId()); + synchronized (HtmlUnitRequestBuilder.this.sessions) { + HtmlUnitRequestBuilder.this.sessions.remove(getId()); } removeSessionCookie(request, getId()); } } private CookieManager getCookieManager() { - return webClient.getCookieManager(); + return this.webClient.getCookieManager(); } - private static final Pattern LOCALE_PATTERN = Pattern.compile("^\\s*(\\w{2})(?:-(\\w{2}))?(?:;q=(\\d+\\.\\d+))?$"); } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java index e9236d31a87..9764cea5c67 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilder.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,18 +13,22 @@ * License for the specific language governing permissions and limitations under * the License. */ -package org.springframework.test.web.servlet.htmlunit; -import com.gargoylesoftware.htmlunit.WebClient; +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; + /** - * Simplifies creating a WebClient that delegates to a MockMvc instance. + * {@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 { @@ -42,56 +46,63 @@ public class MockMvcWebClientBuilder extends MockMvcWebConnectionBuilderSupport< } /** - * Creates a new instance with a WebApplicationContext. - * - * @param context the WebApplicationContext to use. Cannot be null. + * 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); } /** - * Creates a new instance using a WebApplicationContext - * @param context the WebApplicationContext to create a MockMvc instance from. - * @param configurer the MockMvcConfigurer to apply - * Cannot be null. - * @return the MockMvcWebClientBuilder to use + * 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); } /** - * Creates a new instance with a MockMvc instance. - * - * @param mockMvc the MockMvc to use. Cannot be null. + * 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); } /** - * Creates a WebClient that uses the provided MockMvc for any matching requests and a - * WebClient with all the default settings for any other request. - * - * @return the WebClient to use + * 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()); } /** - * Creates a WebClient that uses the provided MockMvc for any matching requests and the - * provided WebClient for any other request. - * - * @param webClient The WebClient to delegate to for requests that do not match. Cannot be null. - * + * 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; } + } \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java index 1ab9494dc06..d277f4cef8e 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnection.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,18 +13,12 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.springframework.test.web.servlet.htmlunit; import java.io.IOException; import java.util.HashMap; import java.util.Map; -import javax.servlet.http.HttpServletRequest; - -import com.gargoylesoftware.htmlunit.CookieManager; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.WebConnection; -import com.gargoylesoftware.htmlunit.WebRequest; -import com.gargoylesoftware.htmlunit.WebResponse; import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; @@ -34,31 +28,33 @@ 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; + /** - *

- * Allows {@link MockMvc} to transform a {@link WebRequest} into a {@link WebResponse}. This is the core integration - * with HTML Unit. - *

- *

- * Example usage can be seen below: - *

+ * {@code MockMvcWebConnection} enables {@link MockMvc} to transform a + * {@link WebRequest} into a {@link WebResponse}. + *

This is the core integration with HtmlUnit. + *

Example usage can be seen below. * - *

+ * 
  * WebClient webClient = new WebClient();
  * MockMvc mockMvc = ...
  * MockMvcWebConnection webConnection = new MockMvcWebConnection(mockMvc);
  * mockConnection.setWebClient(webClient);
  * webClient.setWebConnection(webConnection);
  *
- * ... use webClient as normal ...
+ * // Use webClient as normal ...
  * 
* * @author Rob Winch + * @author Sam Brannen * @since 4.2 * @see WebConnectionHtmlUnitDriver */ public final class MockMvcWebConnection implements WebConnection { - private WebClient webClient; private final Map sessions = new HashMap(); @@ -66,26 +62,32 @@ public final class MockMvcWebConnection implements WebConnection { private final String contextPath; + private WebClient webClient; + + /** - * Creates a new instance that assumes the context root of the application is "". For example, - * the URL http://localhost/test/this would use "" as the context root. - * - * @param mockMvc the MockMvc instance to use + * Create a new instance that assumes the context path of the application + * is {@code ""} (i.e., the root context). + *

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, ""); } /** - * Creates a new instance with a specified context root. - * - * @param mockMvc the MockMvc instance to use - * @param contextPath the contextPath to use. The value may be null in which case the first path segment of the URL is turned - * into the contextPath. Otherwise it must conform to {@link HttpServletRequest#getContextPath()} which states it - * can be empty string or it must start with a "/" and not end in a "/". + * Create a new instance with the specified context path. + *

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 cannot be null"); + Assert.notNull(mockMvc, "mockMvc must not be null"); validateContextPath(contextPath); this.webClient = new WebClient(); @@ -95,13 +97,13 @@ public final class MockMvcWebConnection implements WebConnection { public WebResponse getResponse(WebRequest webRequest) throws IOException { long startTime = System.currentTimeMillis(); - HtmlUnitRequestBuilder requestBuilder = new HtmlUnitRequestBuilder(sessions, webClient, webRequest); - requestBuilder.setContextPath(contextPath); + HtmlUnitRequestBuilder requestBuilder = new HtmlUnitRequestBuilder(this.sessions, this.webClient, webRequest); + requestBuilder.setContextPath(this.contextPath); MockHttpServletResponse httpServletResponse = getResponse(requestBuilder); String forwardedUrl = httpServletResponse.getForwardedUrl(); - while(forwardedUrl != null) { + while (forwardedUrl != null) { requestBuilder.setForwardPostProcessor(new ForwardRequestPostProcessor(forwardedUrl)); httpServletResponse = getResponse(requestBuilder); forwardedUrl = httpServletResponse.getForwardedUrl(); @@ -111,18 +113,14 @@ public final class MockMvcWebConnection implements WebConnection { } public void setWebClient(WebClient webClient) { - Assert.notNull(webClient, "webClient cannot be null"); + Assert.notNull(webClient, "webClient must not be null"); this.webClient = webClient; } - private CookieManager getCookieManager() { - return webClient.getCookieManager(); - } - private MockHttpServletResponse getResponse(RequestBuilder requestBuilder) throws IOException { ResultActions resultActions; try { - resultActions = mockMvc.perform(requestBuilder); + resultActions = this.mockMvc.perform(requestBuilder); } catch (Exception e) { throw (IOException) new IOException(e.getMessage()).initCause(e); @@ -132,19 +130,23 @@ public final class MockMvcWebConnection implements WebConnection { } /** - * Performs validation on the contextPath - * - * @param contextPath the contextPath to validate + * Validate the supplied {@code contextPath}. + *

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 */ - private static void validateContextPath(String contextPath) { + static void validateContextPath(String contextPath) { if (contextPath == null || "".equals(contextPath)) { return; } - if (contextPath.endsWith("/")) { - throw new IllegalArgumentException("contextPath cannot end with /. Got '" + contextPath + "'"); - } if (!contextPath.startsWith("/")) { - throw new IllegalArgumentException("contextPath must start with /. Got '" + contextPath + "'"); + throw new IllegalArgumentException("contextPath '" + contextPath + "' must start with '/'."); + } + if (contextPath.endsWith("/")) { + throw new IllegalArgumentException("contextPath '" + contextPath + "' must not end with '/'."); } } + } \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java index df2055fd45a..10bedebaf4f 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionBuilderSupport.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,72 +13,80 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.springframework.test.web.servlet.htmlunit; import java.util.ArrayList; import java.util.List; -import com.gargoylesoftware.htmlunit.WebConnection; - import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcConfigurer; import org.springframework.util.Assert; import org.springframework.web.context.WebApplicationContext; +import com.gargoylesoftware.htmlunit.WebConnection; + /** - * Makes it easy to create a WebConnection that uses MockMvc and optionally delegates to - * a real WebConnection for specific requests. The default is to use MockMvc for any host - * that is "localhost" and otherwise use a real WebConnection. + * 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. + * + *

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> { - private String contextPath = ""; private final MockMvc mockMvc; - private List mockMvcRequestMatchers = new ArrayList(); + private final List mockMvcRequestMatchers = new ArrayList(); + + private String contextPath = ""; private boolean alwaysUseMockMvc; + /** - * Creates a new instance using a MockMvc instance - * - * @param mockMvc the MockMvc instance to use. Cannot be null. + * 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 cannot be null"); + Assert.notNull(mockMvc, "mockMvc must not be null"); this.mockMvc = mockMvc; this.mockMvcRequestMatchers.add(new HostRequestMatcher("localhost")); } /** - * Creates a new instance using a WebApplicationContext - * @param context the WebApplicationContext to create a MockMvc instance from. - * Cannot be null. + * 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()); } /** - * Creates a new instance using a WebApplicationContext - * @param context the WebApplicationContext to create a MockMvc instance from. - * @param configurer the MockMvcConfigurer to apply - * Cannot be null. + * 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()); } /** - * The context path to use. Default is "". If the value is null, then the first path + * Set the context path to use. + *

If the supplied value is {@code null} or empty, the first path * segment of the request URL is assumed to be the context path. - * - * @param contextPath the context path to use. - * @return the builder for further customization + *

Default is {@code ""}. + * @param contextPath the context path to use + * @return this builder for further customization */ @SuppressWarnings("unchecked") public T contextPath(String contextPath) { @@ -87,9 +95,9 @@ public abstract class MockMvcWebConnectionBuilderSupport delegates = new ArrayList(mockMvcRequestMatchers.size()); - for(WebRequestMatcher matcher : mockMvcRequestMatchers) { + List delegates = new ArrayList( + this.mockMvcRequestMatchers.size()); + for (WebRequestMatcher matcher : this.mockMvcRequestMatchers) { delegates.add(new DelegatingWebConnection.DelegateWebConnection(matcher, mockMvcWebConnection)); } return new DelegatingWebConnection(defaultConnection, delegates); } + } \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java index f619641bb63..7cf99f13291 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilder.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.springframework.test.web.servlet.htmlunit; import java.io.IOException; @@ -20,32 +21,38 @@ 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; -import org.springframework.http.HttpStatus; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.util.Assert; - /** * @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 httpServletResponse) { - Assert.notNull(webRequest, "webRequest"); - Assert.notNull(httpServletResponse, "httpServletResponse cannot be null"); + + 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 = httpServletResponse; + this.response = response; } public WebResponse build() throws IOException { @@ -56,38 +63,42 @@ final class MockWebResponseBuilder { private WebResponseData webResponseData() throws IOException { List responseHeaders = responseHeaders(); - int statusCode = response.getRedirectedUrl() == null ? response.getStatus() : 301; + int statusCode = (this.response.getRedirectedUrl() != null ? HttpStatus.MOVED_PERMANENTLY.value() + : this.response.getStatus()); String statusMessage = statusMessage(statusCode); - return new WebResponseData(response.getContentAsByteArray(), statusCode, statusMessage, responseHeaders); + return new WebResponseData(this.response.getContentAsByteArray(), statusCode, statusMessage, responseHeaders); } private String statusMessage(int statusCode) { - String errorMessage = response.getErrorMessage(); - if (errorMessage != null) { + String errorMessage = this.response.getErrorMessage(); + if (StringUtils.hasText(errorMessage)) { return errorMessage; } + try { return HttpStatus.valueOf(statusCode).getReasonPhrase(); } - catch (IllegalArgumentException useDefault) { + catch (IllegalArgumentException ex) { + // ignore } - ; - return "N/A"; + + return DEFAULT_STATUS_MESSAGE; } private List responseHeaders() { - Collection headerNames = response.getHeaderNames(); + Collection headerNames = this.response.getHeaderNames(); List responseHeaders = new ArrayList(headerNames.size()); for (String headerName : headerNames) { - List headerValues = response.getHeaderValues(headerName); + List headerValues = this.response.getHeaderValues(headerName); for (Object value : headerValues) { responseHeaders.add(new NameValuePair(headerName, String.valueOf(value))); } } - String location = response.getRedirectedUrl(); + String location = this.response.getRedirectedUrl(); if (location != null) { responseHeaders.add(new NameValuePair("Location", location)); } return responseHeaders; } + } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcher.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcher.java index c4593bd4d8b..13d6b6e5463 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcher.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcher.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.springframework.test.web.servlet.htmlunit; import java.util.regex.Pattern; @@ -20,22 +21,25 @@ import java.util.regex.Pattern; import com.gargoylesoftware.htmlunit.WebRequest; /** - *

- * An implementation of WebRequestMatcher that allows matching on WebRequest#getUrl().toExternalForm() using a regular expression. For example, if you would like to match on the domain code.jquery.com, you might want to use the following:

+ * A {@link WebRequestMatcher} that allows matching on + * {@code WebRequest#getUrl().toExternalForm()} using a regular expression. + * + *

For example, if you would like to match on the domain {@code code.jquery.com}, + * you might want to use the following. * - *

- * WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*");
- * 
+ *
WebRequestMatcher cdnMatcher = new UrlRegexRequestMatcher(".*?//code.jquery.com/.*");
* * @author Rob Winch + * @author Sam Brannen * @since 4.2 * @see org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection */ public final class UrlRegexRequestMatcher implements WebRequestMatcher { - private Pattern pattern; + + private final Pattern pattern; public UrlRegexRequestMatcher(String regex) { - pattern = Pattern.compile(regex); + this.pattern = Pattern.compile(regex); } public UrlRegexRequestMatcher(Pattern pattern) { @@ -45,6 +49,7 @@ public final class UrlRegexRequestMatcher implements WebRequestMatcher { @Override public boolean matches(WebRequest request) { String url = request.getUrl().toExternalForm(); - return pattern.matcher(url).matches(); + return this.pattern.matcher(url).matches(); } + } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/WebRequestMatcher.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/WebRequestMatcher.java index 034295490aa..f4f3a328882 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/WebRequestMatcher.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/WebRequestMatcher.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,22 +13,24 @@ * License for the specific language governing permissions and limitations under * the License. */ - package org.springframework.test.web.servlet.htmlunit; + +package org.springframework.test.web.servlet.htmlunit; import com.gargoylesoftware.htmlunit.WebRequest; /** - * Strategy to match on a WebRequest + * Strategy for matching on a {@link WebRequest}. * * @author Rob Winch * @since 4.2 */ public interface WebRequestMatcher { + /** - * Return true if matches on WebRequest, else false - * - * @param request the WebRequest to attempt to match on - * @return true if matches on WebRequest, else false + * 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); + } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/package-info.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/package-info.java index 40810e0127f..e94ee986675 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/package-info.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/package-info.java @@ -1,7 +1,6 @@ /** - * Support for MockMvc and HtmlUnit integration - * - * @author Rob Winch - * @since 4.2 + * 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; \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java index a732863c111..711ec484eaa 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilder.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,9 +13,9 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.springframework.test.web.servlet.htmlunit.webdriver; -import com.gargoylesoftware.htmlunit.BrowserVersion; import org.openqa.selenium.htmlunit.HtmlUnitDriver; import org.springframework.test.web.servlet.MockMvc; @@ -23,18 +23,27 @@ import org.springframework.test.web.servlet.htmlunit.MockMvcWebConnectionBuilder 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 HtmlUnitDriver that will delegate to MockMvc and - * optionally delegate to an actual connection for specific requests. + * Convenience class for building an {@link HtmlUnitDriver} that delegates + * to {@link MockMvc} and optionally delegates to an actual connection for + * specific requests. * - * By default localhost will delegate to MockMvc and any other URL will delegate + *

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 { + private boolean javascriptEnabled = true; + protected MockMvcHtmlUnitDriverBuilder(MockMvc mockMvc) { super(mockMvc); } @@ -48,9 +57,8 @@ public class MockMvcHtmlUnitDriverBuilder extends MockMvcWebConnectionBuilderSup } /** - * Creates a new instance with a WebApplicationContext. - * - * @param context the WebApplicationContext to use. Cannot be null. + * 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) { @@ -58,20 +66,21 @@ public class MockMvcHtmlUnitDriverBuilder extends MockMvcWebConnectionBuilderSup } /** - * Creates a new instance using a WebApplicationContext - * @param context the WebApplicationContext to create a MockMvc instance from. - * @param configurer the MockMvcConfigurer to apply - * Cannot be null. + * 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) { + public static MockMvcHtmlUnitDriverBuilder webAppContextSetup(WebApplicationContext context, + MockMvcConfigurer configurer) { return new MockMvcHtmlUnitDriverBuilder(context, configurer); } /** - * Creates a new instance with a MockMvc instance. - * - * @param mockMvc the MockMvc to use. Cannot be null. + * 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) { @@ -79,8 +88,8 @@ public class MockMvcHtmlUnitDriverBuilder extends MockMvcWebConnectionBuilderSup } /** - * Specifies if JavaScript should be enabled or not. Default is true. - * + * Specify whether JavaScript should be enabled. + *

Default is {@code true}. * @param javascriptEnabled if JavaScript should be enabled or not. * @return the builder for further customizations */ @@ -90,10 +99,10 @@ public class MockMvcHtmlUnitDriverBuilder extends MockMvcWebConnectionBuilderSup } /** - * Creates a new HtmlUnitDriver with the BrowserVersion set to CHROME. For additional - * configuration options, use configureDriver. - * - * @return the HtmlUnitDriver to use + * Create a new {@link HtmlUnitDriver} with the {@link BrowserVersion} + * set to {@link BrowserVersion#CHROME CHROME}. + *

For additional configuration options, use {@link #configureDriver}. + * @return the {@code HtmlUnitDriver} to use * @see #configureDriver(WebConnectionHtmlUnitDriver) */ public HtmlUnitDriver createDriver() { @@ -101,14 +110,14 @@ public class MockMvcHtmlUnitDriverBuilder extends MockMvcWebConnectionBuilderSup } /** - * Configures an existing WebConnectionHtmlUnitDriver. - * + * Configure an existing {@link WebConnectionHtmlUnitDriver}. * @param driver the WebConnectionHtmlUnitDriver to configure - * @return the HtmlUnitDriver to use + * @return the {@code HtmlUnitDriver} to use */ public HtmlUnitDriver configureDriver(WebConnectionHtmlUnitDriver driver) { driver.setJavascriptEnabled(javascriptEnabled); driver.setWebConnection(createConnection(driver.getWebConnection())); return driver; } + } \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java index 8b48e209212..bdfc41dd9b8 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriver.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,31 +13,38 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.springframework.test.web.servlet.htmlunit.webdriver; -import com.gargoylesoftware.htmlunit.BrowserVersion; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.WebConnection; import org.openqa.selenium.Capabilities; import org.openqa.selenium.htmlunit.HtmlUnitDriver; +import org.springframework.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; + /** - *

- * Allows configuring the WebConnection for an HtmlUnitDriver instance. This is useful - * because it allows a MockMvcWebConnection to be injected. - *

+ * {@code WebConnectionHtmlUnitDriver} enables configuration of the + * {@link WebConnection} for an {@link HtmlUnitDriver} instance. + * + *

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 version) { - super(version); + + public WebConnectionHtmlUnitDriver(BrowserVersion browserVersion) { + super(browserVersion); } public WebConnectionHtmlUnitDriver() { @@ -52,45 +59,48 @@ public class WebConnectionHtmlUnitDriver extends HtmlUnitDriver { } /** - * Captures the WebClient that is used so that its WebConnection is accessible. - * - * @param client The client to modify - * @return The modified client + * 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 client) { - webClient = super.modifyWebClient(client); - webClient = configureWebClient(webClient); - return webClient; + protected final WebClient modifyWebClient(WebClient webClient) { + this.webClient = super.modifyWebClient(webClient); + this.webClient = configureWebClient(this.webClient); + return this.webClient; } /** - * Subclasses can override this method to customise the WebClient that the HtmlUnit - * driver uses. - * - * @param client The client to modify - * @return The modified client + * Configure the supplied {@link WebClient}. + *

The default implementation simply returns the supplied client + * unmodified. + *

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; } /** - * Allows accessing the current WebConnection - * - * @return the current WebConnection + * Access the current {@link WebConnection} for the {@link WebClient}. + * @return the current {@code WebConnection} */ public WebConnection getWebConnection() { - return webClient.getWebConnection(); + return this.webClient.getWebConnection(); } /** - * Sets the WebConnection to be used. - * - * @param webConnection the WebConnection to use. Cannot be null. + * 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 cannot be null"); + Assert.notNull(webConnection, "WebConnection must not be null"); this.webClient.setWebConnection(webConnection); } + } \ No newline at end of file diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/package-info.java b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/package-info.java index 9b4861d7e29..afc0f6881df 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/package-info.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/htmlunit/webdriver/package-info.java @@ -1,7 +1,7 @@ /** - * Support for MockMvc and HtmlUnitDriver - * - * @author Rob Winch - * @since 4.2 + * 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; \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java index 98679a05113..eed3271f979 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/DelegatingWebConnectionTests.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,11 +13,24 @@ * 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; @@ -26,29 +39,20 @@ import com.gargoylesoftware.htmlunit.WebRequest; import com.gargoylesoftware.htmlunit.WebResponse; import com.gargoylesoftware.htmlunit.WebResponseData; import com.gargoylesoftware.htmlunit.util.NameValuePair; + import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.sameInstance; import static org.hamcrest.Matchers.isEmptyString; import static org.hamcrest.core.IsNot.not; import static org.junit.Assert.assertThat; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; import static org.mockito.Mockito.*; -import org.mockito.runners.MockitoJUnitRunner; -import static org.springframework.test.web.servlet.htmlunit.DelegatingWebConnection.DelegateWebConnection; - -import org.springframework.stereotype.Controller; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; /** * @author Rob Winch + * @since 4.2 */ @RunWith(MockitoJUnitRunner.class) public class DelegatingWebConnectionTests { - private DelegatingWebConnection webConnection; @Mock private WebRequestMatcher matcher1; @@ -61,6 +65,7 @@ public class DelegatingWebConnectionTests { @Mock private WebConnection connection2; + private DelegatingWebConnection webConnection; private WebRequest request; private WebResponse expectedResponse; @@ -134,4 +139,5 @@ public class DelegatingWebConnectionTests { @Controller static class TestController {} -} \ No newline at end of file + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/ForwardController.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/ForwardController.java index 56dba457610..367b7177385 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/ForwardController.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/ForwardController.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.springframework.test.web.servlet.htmlunit; import org.springframework.stereotype.Controller; @@ -20,12 +21,14 @@ import org.springframework.web.bind.annotation.RequestMapping; /** * @author Rob Winch + * @since 4.2 */ - @Controller +@Controller public class ForwardController { @RequestMapping("/forward") public String forward() { return "forward:/"; } + } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HelloController.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HelloController.java index 95c0f805728..80b64c75131 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HelloController.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HelloController.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.springframework.test.web.servlet.htmlunit; import javax.servlet.http.HttpServletRequest; @@ -23,12 +24,15 @@ 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"; } + } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcherTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcherTests.java index 12691146905..5d69bd953c1 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcherTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HostRequestMatcherTests.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,20 +13,21 @@ * 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; -import org.junit.Test; - -import org.springframework.test.web.servlet.htmlunit.HostRequestMatcher; -import org.springframework.test.web.servlet.htmlunit.WebRequestMatcher; /** * @author Rob Winch + * @since 4.2 */ public class HostRequestMatcherTests { @@ -76,4 +77,5 @@ public class HostRequestMatcherTests { matches = matcher.matches(new WebRequest(new URL("http://localhost:9090/jquery-1.11.0.min.js"))); assertThat(matches, equalTo(false));; } -} \ No newline at end of file + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java index 5572fa3f63a..a7810b65610 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/HtmlUnitRequestBuilderTests.java @@ -13,6 +13,7 @@ * 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; @@ -28,19 +29,10 @@ import javax.servlet.ServletContext; import javax.servlet.http.Cookie; import javax.servlet.http.HttpSession; -import com.gargoylesoftware.htmlunit.CookieManager; -import com.gargoylesoftware.htmlunit.HttpMethod; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.WebRequest; -import com.gargoylesoftware.htmlunit.util.NameValuePair; import org.apache.commons.io.IOUtils; import org.apache.http.auth.UsernamePasswordCredentials; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertThat; import org.junit.Before; import org.junit.Test; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; - import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpSession; import org.springframework.mock.web.MockServletContext; @@ -48,12 +40,21 @@ 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; @@ -64,6 +65,7 @@ public class HtmlUnitRequestBuilderTests { private HtmlUnitRequestBuilder requestBuilder; + @Before public void setUp() throws Exception { sessions = new HashMap<>(); @@ -779,4 +781,5 @@ public class HtmlUnitRequestBuilderTests { private String getContextPath() { return (String) ReflectionTestUtils.getField(requestBuilder, "contextPath"); } + } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java index a6343cca24d..442c6072d13 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcConnectionBuilderSupportTests.java @@ -13,24 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.test.web.servlet.htmlunit; + +package org.springframework.test.web.servlet.htmlunit; import java.io.IOException; import java.net.URL; + import javax.servlet.http.HttpServletRequest; -import com.gargoylesoftware.htmlunit.WebConnection; -import com.gargoylesoftware.htmlunit.WebRequest; -import com.gargoylesoftware.htmlunit.WebResponse; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.assertThat; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import static org.mockito.Mockito.mock; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.test.context.ContextConfiguration; @@ -43,13 +36,26 @@ 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; @@ -152,4 +158,5 @@ public class MockMvcConnectionBuilderSupportTests { } } } -} \ No newline at end of file + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java index c569052ca21..71ebb43e3de 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebClientBuilderTests.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,18 +13,13 @@ * License for the specific language governing permissions and limitations under * the License. */ - package org.springframework.test.web.servlet.htmlunit; + +package org.springframework.test.web.servlet.htmlunit; import java.io.IOException; import java.net.URL; import javax.servlet.http.HttpServletRequest; -import com.gargoylesoftware.htmlunit.WebClient; -import com.gargoylesoftware.htmlunit.WebRequest; -import com.gargoylesoftware.htmlunit.WebResponse; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertThat; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -41,13 +36,23 @@ 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; @@ -114,4 +119,5 @@ public class MockMvcWebClientBuilderTests { } } } -} \ No newline at end of file + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java index 04344cf0a1f..c9b1386dae7 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockMvcWebConnectionTests.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,24 +13,28 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.springframework.test.web.servlet.htmlunit; import java.io.IOException; -import com.gargoylesoftware.htmlunit.Page; -import com.gargoylesoftware.htmlunit.WebClient; -import static org.hamcrest.CoreMatchers.equalTo; -import static org.junit.Assert.assertThat; import org.junit.Before; import org.junit.Test; - import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +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; @@ -89,4 +93,5 @@ public class MockMvcWebConnectionTests { public void contextPathEndsWithSlash() throws IOException { new MockMvcWebConnection(mockMvc, "/context/"); } -} \ No newline at end of file + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java index b4134894a87..770f1e06ee8 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/MockWebResponseBuilderTests.java @@ -4,30 +4,32 @@ * 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 + * 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; -import org.junit.Before; -import org.junit.Test; - -import org.springframework.mock.web.MockHttpServletResponse; /** - * * @author Rob Winch + * @since 4.2 */ public class MockWebResponseBuilderTests { @@ -132,4 +134,5 @@ public class MockWebResponseBuilderTests { assertThat(webResponse.getWebRequest(), equalTo(webRequest));; } + } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcherTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcherTests.java index 40ce0056143..76c9a5e757a 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcherTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/UrlRegexRequestMatcherTests.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,20 +13,21 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.springframework.test.web.servlet.htmlunit; import java.net.URL; -import com.gargoylesoftware.htmlunit.WebRequest; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.equalTo; import org.junit.Test; -import org.springframework.test.web.servlet.htmlunit.UrlRegexRequestMatcher; -import org.springframework.test.web.servlet.htmlunit.WebRequestMatcher; +import com.gargoylesoftware.htmlunit.WebRequest; + +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; /** * @author Rob Winch + * @since 4.2 */ public class UrlRegexRequestMatcherTests { @@ -40,4 +41,5 @@ public class UrlRegexRequestMatcherTests { matches = cdnMatcher.matches(new WebRequest(new URL("http://localhost/jquery-1.11.0.min.js"))); assertThat(matches, equalTo(false)); } + } \ No newline at end of file diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java index 871273b134c..42c3e265923 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/MockMvcHtmlUnitDriverBuilderTests.java @@ -5,7 +5,7 @@ * 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 + * 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 @@ -13,6 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ + package org.springframework.test.web.servlet.htmlunit.webdriver; import java.io.IOException; @@ -39,6 +40,7 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc; /** * @author Rob Winch + * @since 4.2 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriverTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriverTests.java index afcdf185c7d..627a533f113 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriverTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/htmlunit/webdriver/WebConnectionHtmlUnitDriverTests.java @@ -1,25 +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.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 org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; import static org.mockito.Matchers.any; -import org.mockito.Mock; import static org.mockito.Mockito.when; -import org.mockito.runners.MockitoJUnitRunner; /** * @author Rob Winch + * @since 4.2 */ -// tag::junit-spring-setup[] @RunWith(MockitoJUnitRunner.class) public class WebConnectionHtmlUnitDriverTests { + @Mock WebConnection connection; @@ -52,4 +72,5 @@ public class WebConnectionHtmlUnitDriverTests { public void setWebConnectionNull() { driver.setWebConnection(null); } + } \ No newline at end of file