MockClientHttpRequest
and
+ * MockClientHttpResponse
.
+ */
+package org.springframework.mock.http.client;
+
diff --git a/src/main/java/org/springframework/mock/http/package-info.java b/src/main/java/org/springframework/mock/http/package-info.java
new file mode 100644
index 0000000..6f0868a
--- /dev/null
+++ b/src/main/java/org/springframework/mock/http/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2011-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.
+ */
+
+/**
+ * Mock implementations of client/server-side HTTP abstractions.
+ * This package contains MockHttpInputMessage
and
+ * MockHttpOutputMessage
.
+ */
+package org.springframework.mock.http;
+
diff --git a/src/main/java/org/springframework/test/web/AssertionErrors.java b/src/main/java/org/springframework/test/web/AssertionErrors.java
index 2f4d0fd..cc10978 100644
--- a/src/main/java/org/springframework/test/web/AssertionErrors.java
+++ b/src/main/java/org/springframework/test/web/AssertionErrors.java
@@ -15,11 +15,9 @@
*/
package org.springframework.test.web;
-import java.util.Map;
-
/**
* JUnit independent assertion class.
- *
+ *
* @author Lukas Krecan
* @author Arjen Poutsma
*/
@@ -39,7 +37,7 @@ public static void fail(String message) {
/**
* Fails a test with the given message passing along expected and actual values to be added to the message.
- *
+ *
* @param message the message
* @param expected the expected value
* @param actual the actual value
@@ -76,33 +74,5 @@ public static void assertEquals(String message, Object expected, Object actual)
}
fail(message, expected, actual);
}
-
- /**
- * Assert the given names are present in the provided map of named values.
- * @param label a label describing the named value - e.g. "Request attribute", "Response header", etc.
- * @param namedValues a map containing all name-value pairs
- * @param names the names to check
- */
- public static void assertNameValuesPresent(String label, MapBelow is an example: + *
+ * RestTemplate restTemplate = new RestTemplate() + * MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate); + * + * mockServer.expect(requestTo("/hotels/42")).andExpect(method(HttpMethod.GET)) + * .andRespond(withSuccess("{ \"id\" : \"42\", \"name\" : \"Holiday Inn\"}", MediaType.APPLICATION_JSON)); + * + * Hotel hotel = restTemplate.getForObject("/hotels/{id}", Hotel.class, 42); + * // Use the hotel instance... + * + * mockServer.verify(); + * + *To create an instance of this class, use {@link #createServer(RestTemplate)} + * and provide the {@code RestTemplate} to set up for the mock testing. + * + *
After that use {@link #expect(RequestMatcher)} and fluent API methods + * {@link ResponseActions#andExpect(RequestMatcher) andExpect(RequestMatcher)} and + * {@link ResponseActions#andRespond(ResponseCreator) andRespond(ResponseCreator)} + * to set up request expectations and responses, most likely relying on the default + * {@code RequestMatcher} implementations provided in {@link RequestMatchers} + * and the {@code ResponseCreator} implementations provided in + * {@link ResponseCreators} both of which can be statically imported. + * + *
At the end of the test use {@link #verify()} to ensure all expected + * requests were actually performed. + * + *
Note that because of the fluent API offered by this class (and related + * classes), you can typically use the Code Completion features (i.e. + * ctrl-space) in your IDE to set up the mocks. + * + *
Credits: The client-side REST testing support was + * inspired by and initially based on similar code in the Spring WS project for + * client-side tests involving the {@code WebServiceTemplate}. + * + * @author Craig Walls + * @author Rossen Stoyanchev + */ +public class MockRestServiceServer { + + private final List
expectedRequests = + new LinkedList (); + + private final List actualRequests = + new LinkedList (); + + + /** + * Private constructor. + * @see #createServer(RestTemplate) + * @see #createServer(RestGatewaySupport) + */ + private MockRestServiceServer() { + } + + /** + * Create a {@code MockRestServiceServer} and set up the given + * {@code RestTemplate} with a mock {@link ClientHttpRequestFactory}. + * + * @param restTemplate the RestTemplate to set up for mock testing + * @return the created mock server + */ + public static MockRestServiceServer createServer(RestTemplate restTemplate) { + Assert.notNull(restTemplate, "'restTemplate' must not be null"); + + MockRestServiceServer mockServer = new MockRestServiceServer(); + RequestMatcherClientHttpRequestFactory factory = mockServer.new RequestMatcherClientHttpRequestFactory(); + + restTemplate.setRequestFactory(factory); + + return mockServer; + } + + /** + * Create a {@code MockRestServiceServer} and set up the given + * {@code RestGatewaySupport} with a mock {@link ClientHttpRequestFactory}. + * + * @param restGateway the REST gateway to set up for mock testing + * @return the created mock server + */ + public static MockRestServiceServer createServer(RestGatewaySupport restGateway) { + Assert.notNull(restGateway, "'gatewaySupport' must not be null"); + return createServer(restGateway.getRestTemplate()); + } + + /** + * Set up a new HTTP request expectation. The returned {@link ResponseActions} + * is used to set up further expectations and to define the response. + * + * This method may be invoked multiple times before starting the test, i.e. + * before using the {@code RestTemplate}, to set up expectations for multiple + * requests. + * + * @param requestMatcher a request expectation, see {@link RequestMatchers} + * @return used to set up further expectations or to define a response + */ + public ResponseActions expect(RequestMatcher requestMatcher) { + Assert.state(this.actualRequests.isEmpty(), "Can't add more expected requests with test already underway"); + RequestMatcherClientHttpRequest request = new RequestMatcherClientHttpRequest(requestMatcher); + this.expectedRequests.add(request); + return request; + } + + /** + * Verify that all expected requests set up via + * {@link #expect(RequestMatcher)} were indeed performed. + * + * @throws AssertionError when some expectations were not met + */ + public void verify() { + if (this.expectedRequests.isEmpty() || this.expectedRequests.equals(this.actualRequests)) { + return; + } + throw new AssertionError(getVerifyMessage()); + } + + private String getVerifyMessage() { + StringBuilder sb = new StringBuilder("Further request(s) expected\n"); + + if (this.actualRequests.size() > 0) { + sb.append("The following "); + } + sb.append(this.actualRequests.size()).append(" out of "); + sb.append(this.expectedRequests.size()).append(" were executed"); + + if (this.actualRequests.size() > 0) { + sb.append(":\n"); + for (RequestMatcherClientHttpRequest request : this.actualRequests) { + sb.append(request.toString()).append("\n"); + } + } + + return sb.toString(); + } + + + /** + * Mock ClientHttpRequestFactory that creates requests by iterating + * over the list of expected {@link RequestMatcherClientHttpRequest}'s. + */ + private class RequestMatcherClientHttpRequestFactory implements ClientHttpRequestFactory { + + private Iterator
requestIterator; + + public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { + Assert.notNull(uri, "'uri' must not be null"); + Assert.notNull(httpMethod, "'httpMethod' must not be null"); + + if (this.requestIterator == null) { + this.requestIterator = MockRestServiceServer.this.expectedRequests.iterator(); + } + if (!this.requestIterator.hasNext()) { + throw new AssertionError("No further requests expected"); + } + + RequestMatcherClientHttpRequest request = this.requestIterator.next(); + request.setURI(uri); + request.setMethod(httpMethod); + + MockRestServiceServer.this.actualRequests.add(request); + + return request; + } + } + +} diff --git a/src/main/java/org/springframework/test/web/client/RequestMatcher.java b/src/main/java/org/springframework/test/web/client/RequestMatcher.java new file mode 100644 index 0000000..085ff06 --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/RequestMatcher.java @@ -0,0 +1,38 @@ +/* + * Copyright 2011 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.client; + +import java.io.IOException; + +import org.springframework.http.client.ClientHttpRequest; + +/** + * A contract for matching requests to expectations. + * + * @author Craig Walls + */ +public interface RequestMatcher { + + /** + * Match the given request against some expectations. + * + * @param request the request to make assertions on + * @throws IOException in case of I/O errors + * @throws AssertionError if expectations are not met + */ + void match(ClientHttpRequest request) throws IOException, AssertionError; + +} diff --git a/src/main/java/org/springframework/test/web/client/RequestMatcherClientHttpRequest.java b/src/main/java/org/springframework/test/web/client/RequestMatcherClientHttpRequest.java new file mode 100644 index 0000000..a5a2236 --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/RequestMatcherClientHttpRequest.java @@ -0,0 +1,78 @@ +/* + * Copyright 2011-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.client; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.mock.http.client.MockClientHttpRequest; +import org.springframework.util.Assert; + +/** + * A specialization of {@code MockClientHttpRequest} that matches the request + * against a set of expectations, via {@link RequestMatcher} instances. The + * expectations are checked when the request is executed. This class also uses a + * {@link ResponseCreator} to create the response. + * + * @author Craig Walls + * @author Rossen Stoyanchev + */ +class RequestMatcherClientHttpRequest extends MockClientHttpRequest implements ResponseActions { + + private final List requestMatchers = new LinkedList (); + + private ResponseCreator responseCreator; + + + public RequestMatcherClientHttpRequest(RequestMatcher requestMatcher) { + Assert.notNull(requestMatcher, "RequestMatcher is required"); + this.requestMatchers.add(requestMatcher); + } + + public ResponseActions andExpect(RequestMatcher requestMatcher) { + Assert.notNull(requestMatcher, "RequestMatcher is required"); + this.requestMatchers.add(requestMatcher); + return this; + } + + public void andRespond(ResponseCreator responseCreator) { + Assert.notNull(responseCreator, "ResponseCreator is required"); + this.responseCreator = responseCreator; + } + + public ClientHttpResponse execute() throws IOException { + + if (this.requestMatchers.isEmpty()) { + throw new AssertionError("No request expectations to execute"); + } + + if (this.responseCreator == null) { + throw new AssertionError("No ResponseCreator was set up. Add it after request expectations, " + + "e.g. MockRestServiceServer.expect(requestTo(\"/foo\")).andRespond(withSuccess())"); + } + + for (RequestMatcher requestMatcher : this.requestMatchers) { + requestMatcher.match(this); + } + + setResponse(this.responseCreator.createResponse(this)); + + return super.execute(); + } + +} diff --git a/src/main/java/org/springframework/test/web/client/ResponseActions.java b/src/main/java/org/springframework/test/web/client/ResponseActions.java new file mode 100644 index 0000000..ba80a77 --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/ResponseActions.java @@ -0,0 +1,38 @@ +/* + * Copyright 2011 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.client; + +/** + * A contract for setting up request expectations and defining a response. + * Implementations can be obtained through {@link MockRestServiceServer#expect(RequestMatcher)}. + * + * @author Craig Walls + */ +public interface ResponseActions { + + /** + * Add a request expectation. + * @return the expectation + */ + ResponseActions andExpect(RequestMatcher requestMatcher); + + /** + * Define the response. + * @param responseCreator the creator of the response + */ + void andRespond(ResponseCreator responseCreator); + +} diff --git a/src/main/java/org/springframework/test/web/client/ResponseCreator.java b/src/main/java/org/springframework/test/web/client/ResponseCreator.java new file mode 100644 index 0000000..d3c3f57 --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/ResponseCreator.java @@ -0,0 +1,38 @@ +/* + * Copyright 2011 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.client; + +import java.io.IOException; + +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.test.web.client.response.ResponseCreators; + +/** + * A contract for creating a {@link ClientHttpResponse}. + * Implementations can be obtained via {@link ResponseCreators}. + * + * @author Craig Walls + */ +public interface ResponseCreator { + + /** + * Create a response for the given request. + * @param request the request + */ + ClientHttpResponse createResponse(ClientHttpRequest request) throws IOException; + +} diff --git a/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java b/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java new file mode 100644 index 0000000..a78a53c --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/match/ContentRequestMatchers.java @@ -0,0 +1,169 @@ +/* + * 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.client.match; + +import static org.springframework.test.web.AssertionErrors.assertEquals; +import static org.springframework.test.web.AssertionErrors.assertTrue; + +import java.io.IOException; + +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.mock.http.client.MockClientHttpRequest; +import org.springframework.test.web.client.RequestMatcher; +import org.springframework.test.web.support.XmlExpectationsHelper; +import org.w3c.dom.Node; + +/** + * Factory for request content {@code RequestMatcher}'s. An instance of this + * class is typically accessed via {@link RequestMatchers#content()}. + * + * @author Rossen Stoyanchev + */ +public class ContentRequestMatchers { + + private final XmlExpectationsHelper xmlHelper; + + + /** + * Class constructor, not for direct instantiation. + * Use {@link RequestMatchers#content()}. + */ + protected ContentRequestMatchers() { + this.xmlHelper = new XmlExpectationsHelper(); + } + + /** + * Assert the request content type as a String. + */ + public RequestMatcher mimeType(String expectedContentType) { + return mimeType(MediaType.parseMediaType(expectedContentType)); + } + + /** + * Assert the request content type as a {@link MediaType}. + */ + public RequestMatcher mimeType(final MediaType expectedContentType) { + return new RequestMatcher() { + public void match(ClientHttpRequest request) throws IOException, AssertionError { + MediaType actualContentType = request.getHeaders().getContentType(); + assertTrue("Content type not set", actualContentType != null); + assertEquals("Content type", expectedContentType, actualContentType); + } + }; + } + + /** + * Get the body of the request as a UTF-8 string and appply the given {@link Matcher}. + */ + public RequestMatcher string(final Matcher super String> matcher) { + return new RequestMatcher() { + public void match(ClientHttpRequest request) throws IOException, AssertionError { + MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; + MatcherAssert.assertThat("Request content", mockRequest.getBodyAsString(), matcher); + } + }; + } + + /** + * Get the body of the request as a UTF-8 string and compare it to the given String. + */ + public RequestMatcher string(String expectedContent) { + return string(Matchers.equalTo(expectedContent)); + } + + /** + * Compare the body of the request to the given byte array. + */ + public RequestMatcher bytes(final byte[] expectedContent) { + return new RequestMatcher() { + public void match(ClientHttpRequest request) throws IOException, AssertionError { + MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; + byte[] content = mockRequest.getBodyAsBytes(); + MatcherAssert.assertThat("Request content", content, Matchers.equalTo(expectedContent)); + } + }; + } + + /** + * Parse the request body and the given String as XML and assert that the + * two are "similar" - i.e. they contain the same elements and attributes + * regardless of order. + * + * Use of this matcher assumes the + * XMLUnit library is available. + * + * @param expectedXmlContent the expected XML content + */ + public RequestMatcher xml(final String expectedXmlContent) { + return new AbstractXmlRequestMatcher() { + @Override + protected void matchInternal(MockClientHttpRequest request) throws Exception { + xmlHelper.assertXmlEqual(expectedXmlContent, request.getBodyAsString()); + } + }; + } + + /** + * Parse the request content as {@link Node} and apply the given {@link Matcher}. + */ + public RequestMatcher node(final Matcher super Node> matcher) { + return new AbstractXmlRequestMatcher() { + @Override + protected void matchInternal(MockClientHttpRequest request) throws Exception { + xmlHelper.assertNode(request.getBodyAsString(), matcher); + } + }; + } + + /** + * Parse the request content as {@link DOMSource} and apply the given {@link Matcher}. + * @see http://code.google.com/p/xml-matchers/ + */ + public RequestMatcher source(final Matcher super Source> matcher) { + return new AbstractXmlRequestMatcher() { + @Override + protected void matchInternal(MockClientHttpRequest request) throws Exception { + xmlHelper.assertSource(request.getBodyAsString(), matcher); + } + }; + } + + /** + * Abstract base class for XML {@link RequestMatcher}'s. + */ + private abstract static class AbstractXmlRequestMatcher implements RequestMatcher { + + public final void match(ClientHttpRequest request) throws IOException, AssertionError { + try { + MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; + matchInternal(mockRequest); + } + catch (Exception e) { + throw new AssertionError("Failed to parse expected or actual XML request content: " + e.getMessage()); + } + } + + protected abstract void matchInternal(MockClientHttpRequest request) throws Exception; + + } +} diff --git a/src/main/java/org/springframework/test/web/client/match/JsonPathRequestMatchers.java b/src/main/java/org/springframework/test/web/client/match/JsonPathRequestMatchers.java new file mode 100644 index 0000000..b7ad09f --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/match/JsonPathRequestMatchers.java @@ -0,0 +1,127 @@ +/* + * Copyright 2011-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.client.match; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; + +import java.io.IOException; +import java.text.ParseException; +import java.util.List; + +import org.hamcrest.Matcher; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.mock.http.client.MockClientHttpRequest; +import org.springframework.test.web.client.RequestMatcher; +import org.springframework.test.web.support.JsonPathExpectationsHelper; + +/** + * Factory methods for request content {@code RequestMatcher}'s using a JSONPath expression. + * An instance of this class is typically accessed via + * {@code RequestMatchers.jsonPath(..)}. + * + * @author Rossen Stoyanchev + */ +public class JsonPathRequestMatchers { + + private JsonPathExpectationsHelper jsonPathHelper; + + + /** + * Class constructor, not for direct instantiation. Use + * {@link RequestMatchers#jsonPath(String, Matcher)} or + * {@link RequestMatchers#jsonPath(String, Object...)}. + * + * @param expression the JSONPath expression + * @param args arguments to parameterize the JSONPath expression with using + * the formatting specifiers defined in + * {@link String#format(String, Object...)} + */ + protected JsonPathRequestMatchers(String expression, Object ... args) { + this.jsonPathHelper = new JsonPathExpectationsHelper(expression, args); + } + + /** + * Evaluate the JSONPath and assert the resulting value with the given {@code Matcher}. + */ + public
RequestMatcher value(final Matcher matcher) { + return new AbstractJsonPathRequestMatcher() { + @Override + protected void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { + jsonPathHelper.assertValue(request.getBodyAsString(), matcher); + } + }; + } + + /** + * Apply the JSONPath and assert the resulting value. + */ + public RequestMatcher value(Object expectedValue) { + return value(equalTo(expectedValue)); + } + + /** + * Apply the JSONPath and assert the resulting value. + */ + public RequestMatcher exists() { + return new AbstractJsonPathRequestMatcher() { + @Override + protected void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { + jsonPathHelper.exists(request.getBodyAsString()); + } + }; + } + + /** + * Evaluate the JSON path and assert the resulting content exists. + */ + public RequestMatcher doesNotExist() { + return new AbstractJsonPathRequestMatcher() { + @Override + protected void matchInternal(MockClientHttpRequest request) throws IOException, ParseException { + jsonPathHelper.doesNotExist(request.getBodyAsString()); + } + }; + } + + /** + * Assert the content at the given JSONPath is an array. + */ + public RequestMatcher isArray() { + return value(instanceOf(List.class)); + } + + + /** + * Abstract base class for JSONPath {@link RequestMatcher}'s. + */ + private abstract static class AbstractJsonPathRequestMatcher implements RequestMatcher { + + public final void match(ClientHttpRequest request) throws IOException, AssertionError { + try { + MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; + matchInternal(mockRequest); + } + catch (ParseException e) { + throw new AssertionError("Failed to parse JSON request content: " + e.getMessage()); + } + } + + protected abstract void matchInternal(MockClientHttpRequest request) throws IOException, ParseException; + + } +} diff --git a/src/main/java/org/springframework/test/web/client/match/RequestMatchers.java b/src/main/java/org/springframework/test/web/client/match/RequestMatchers.java new file mode 100644 index 0000000..156aeaf --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/match/RequestMatchers.java @@ -0,0 +1,262 @@ +/* + * Copyright 2011-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.client.match; + +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.Map; + +import javax.xml.xpath.XPathExpressionException; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.hamcrest.core.IsEqual; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.mock.http.client.MockClientHttpRequest; +import org.springframework.test.web.AssertionErrors; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.test.web.client.RequestMatcher; +import org.springframework.util.Assert; + +/** + * Static, factory methods for {@link RequestMatcher} classes. Typically used to + * provide input for {@link MockRestServiceServer#expect(RequestMatcher)}. + * + * Eclipse users: consider adding this class as a Java editor + * favorite. To navigate, open the Preferences and type "favorites". + * + * @author Craig Walls + * @author Rossen Stoyanchev + */ +public abstract class RequestMatchers { + + + /** + * Private class constructor. + */ + private RequestMatchers() { + } + + /** + * Match to any request. + */ + public static RequestMatcher anything() { + return new RequestMatcher() { + public void match(ClientHttpRequest request) throws AssertionError { + } + }; + } + + /** + * Assert the request URI string with the given matcher. + * + * @param matcher String matcher for the expected URI + * @return the request matcher + */ + public static RequestMatcher requestTo(final Matcher
matcher) { + Assert.notNull(matcher, "'matcher' must not be null"); + return new RequestMatcher() { + public void match(ClientHttpRequest request) throws IOException, AssertionError { + MatcherAssert.assertThat("Request URI", request.getURI().toString(), matcher); + } + }; + } + + /** + * Assert the request URI string. + * + * @param uri the expected URI + * @return the request matcher + */ + public static RequestMatcher requestTo(String uri) { + Assert.notNull(uri, "'uri' must not be null"); + return requestTo(Matchers.equalTo(uri)); + } + + /** + * Assert the {@link HttpMethod} of the request. + * + * @param method the HTTP method + * @return the request matcher + */ + public static RequestMatcher method(final HttpMethod method) { + Assert.notNull(method, "'method' must not be null"); + return new RequestMatcher() { + public void match(ClientHttpRequest request) throws AssertionError { + AssertionErrors.assertEquals("Unexpected HttpMethod", method, request.getMethod()); + } + }; + } + + /** + * Expect a request to the given URI. + * + * @param uri the expected URI + * @return the request matcher + */ + public static RequestMatcher requestTo(final URI uri) { + Assert.notNull(uri, "'uri' must not be null"); + return new RequestMatcher() { + public void match(ClientHttpRequest request) throws IOException, AssertionError { + AssertionErrors.assertEquals("Unexpected request", uri, request.getURI()); + } + }; + } + + /** + * Assert request header values with the given Hamcrest matcher. + */ + public static RequestMatcher header(final String name, final Matcher super String>... matchers) { + return new RequestMatcher() { + public void match(ClientHttpRequest request) { + HttpHeaders headers = request.getHeaders(); + List values = headers.get(name); + AssertionErrors.assertTrue("Expected header <" + name + ">", values != null); + AssertionErrors.assertTrue("Expected header <" + name + "> to have at least <" + matchers.length + + "> values but it has only <" + values.size() + ">", matchers.length <= values.size()); + for (int i = 0 ; i < matchers.length; i++) { + MatcherAssert.assertThat("Request header", headers.get(name).get(i), matchers[i]); + } + } + }; + } + + /** + * Assert request header values. + */ + public static RequestMatcher header(String name, String... values) { + @SuppressWarnings("unchecked") + Matcher super String>[] matchers = new IsEqual[values.length]; + for (int i = 0; i < values.length; i++) { + matchers[i] = Matchers.equalTo(values[i]); + } + return header(name, matchers); + } + + /** + * Access to request body matchers. + */ + public static ContentRequestMatchers content() { + return new ContentRequestMatchers(); + } + + /** + * Access to request body matchers using a JSONPath expression to + * inspect a specific subset of the body. The JSON path expression can be a + * parameterized string using formatting specifiers as defined in + * {@link String#format(String, Object...)}. + * + * @param expression the JSON path optionally parameterized with arguments + * @param args arguments to parameterize the JSON path expression with + */ + public static JsonPathRequestMatchers jsonPath(String expression, Object ... args) { + return new JsonPathRequestMatchers(expression, args); + } + + /** + * Access to request body matchers using a JSONPath expression to + * inspect a specific subset of the body and a Hamcrest match for asserting + * the value found at the JSON path. + * + * @param expression the JSON path expression + * @param matcher a matcher for the value expected at the JSON path + */ + public static RequestMatcher jsonPath(String expression, Matcher matcher) { + return new JsonPathRequestMatchers(expression).value(matcher); + } + + /** + * Access to request body matchers using an XPath to inspect a specific + * subset of the body. The XPath expression can be a parameterized string + * using formatting specifiers as defined in + * {@link String#format(String, Object...)}. + * + * @param expression the XPath optionally parameterized with arguments + * @param args arguments to parameterize the XPath expression with + */ + public static XpathRequestMatchers xpath(String expression, Object... args) throws XPathExpressionException { + return new XpathRequestMatchers(expression, null, args); + } + + /** + * Access to response body matchers using an XPath to inspect a specific + * subset of the body. The XPath expression can be a parameterized string + * using formatting specifiers as defined in + * {@link String#format(String, Object...)}. + * + * @param expression the XPath optionally parameterized with arguments + * @param namespaces namespaces referenced in the XPath expression + * @param args arguments to parameterize the XPath expression with + */ + public static XpathRequestMatchers xpath(String expression, Map namespaces, Object... args) + throws XPathExpressionException { + + return new XpathRequestMatchers(expression, namespaces, args); + } + + + // Deprecated methods .. + + /** + * Expect that the specified request header contains a subtring + * + * @deprecated in favor of {@link #header(String, Matcher...)} + */ + public static RequestMatcher headerContains(final String header, final String substring) { + Assert.notNull(header, "'header' must not be null"); + Assert.notNull(substring, "'substring' must not be null"); + return new RequestMatcher() { + public void match(ClientHttpRequest request) throws AssertionError { + List actualHeaders = request.getHeaders().get(header); + AssertionErrors.assertTrue("Expected header <" + header + "> in request", actualHeaders != null); + + boolean foundMatch = false; + for (String headerValue : actualHeaders) { + if (headerValue.contains(substring)) { + foundMatch = true; + break; + } + } + + AssertionErrors.assertTrue("Expected value containing <" + substring + "> in header <" + header + ">", + foundMatch); + } + }; + } + + /** + * Expect the given request body content. + * + * @deprecated in favor of {@link #content()} as well as {@code jsonPath(..)}, + * and {@code xpath(..)} methods in this class + */ + public static RequestMatcher body(final String body) { + Assert.notNull(body, "'body' must not be null"); + return new RequestMatcher() { + public void match(ClientHttpRequest request) throws AssertionError, IOException { + MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; + AssertionErrors.assertEquals("Unexpected body content", body, mockRequest.getBodyAsString()); + } + }; + } + +} diff --git a/src/main/java/org/springframework/test/web/client/match/XpathRequestMatchers.java b/src/main/java/org/springframework/test/web/client/match/XpathRequestMatchers.java new file mode 100644 index 0000000..1e65808 --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/match/XpathRequestMatchers.java @@ -0,0 +1,177 @@ +/* + * Copyright 2011-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.client.match; + +import java.io.IOException; +import java.util.Map; + +import javax.xml.xpath.XPathExpressionException; + +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.mock.http.client.MockClientHttpRequest; +import org.springframework.test.web.client.RequestMatcher; +import org.springframework.test.web.support.XpathExpectationsHelper; +import org.w3c.dom.Node; + +/** + * Factory methods for request content {@code RequestMatcher}'s using an XPath + * expression. An instance of this class is typically accessed via + * {@code RequestMatchers.xpath(..)}. + * + * @author Rossen Stoyanchev + */ +public class XpathRequestMatchers { + + private final XpathExpectationsHelper xpathHelper; + + + /** + * Class constructor, not for direct instantiation. Use + * {@link RequestMatchers#xpath(String, Object...)} or + * {@link RequestMatchers#xpath(String, Map, Object...)}. + * + * @param expression the XPath expression + * @param namespaces XML namespaces referenced in the XPath expression, or {@code null} + * @param args arguments to parameterize the XPath expression with using the + * formatting specifiers defined in {@link String#format(String, Object...)} + * + * @throws XPathExpressionException + */ + protected XpathRequestMatchers(String expression, Map namespaces, Object ... args) + throws XPathExpressionException { + + this.xpathHelper = new XpathExpectationsHelper(expression, namespaces, args); + } + + /** + * Apply the XPath and assert it with the given {@code Matcher }. + */ + public RequestMatcher node(final Matcher super Node> matcher) { + return new AbstractXpathRequestMatcher() { + @Override + protected void matchInternal(MockClientHttpRequest request) throws Exception { + xpathHelper.assertNode(request.getBodyAsString(), matcher); + } + }; + } + + /** + * Assert that content exists at the given XPath. + */ + public RequestMatcher exists() { + return node(Matchers.notNullValue()); + } + + /** + * Assert that content does not exist at the given XPath. + */ + public RequestMatcher doesNotExist() { + return node(Matchers.nullValue()); + } + + /** + * Apply the XPath and assert the number of nodes found with the given + * {@code Matcher }. + */ + public RequestMatcher nodeCount(final Matcher matcher) { + return new AbstractXpathRequestMatcher() { + @Override + protected void matchInternal(MockClientHttpRequest request) throws Exception { + xpathHelper.assertNodeCount(request.getBodyAsString(), matcher); + } + }; + } + + /** + * Apply the XPath and assert the number of nodes found. + */ + public RequestMatcher nodeCount(int expectedCount) { + return nodeCount(Matchers.equalTo(expectedCount)); + } + + /** + * Apply the XPath and assert the String content found with the given matcher. + */ + public RequestMatcher string(final Matcher super String> matcher) { + return new AbstractXpathRequestMatcher() { + @Override + protected void matchInternal(MockClientHttpRequest request) throws Exception { + xpathHelper.assertString(request.getBodyAsString(), matcher); + } + }; + } + + /** + * Apply the XPath and assert the String content found. + */ + public RequestMatcher string(String value) { + return string(Matchers.equalTo(value)); + } + + /** + * Apply the XPath and assert the number found with the given matcher. + */ + public RequestMatcher number(final Matcher super Double> matcher) { + return new AbstractXpathRequestMatcher() { + @Override + protected void matchInternal(MockClientHttpRequest request) throws Exception { + xpathHelper.assertNumber(request.getBodyAsString(), matcher); + } + }; + } + + /** + * Apply the XPath and assert the number of nodes found. + */ + public RequestMatcher number(Double value) { + return number(Matchers.equalTo(value)); + } + + /** + * Apply the XPath and assert the boolean value found. + */ + public RequestMatcher booleanValue(final Boolean value) { + return new AbstractXpathRequestMatcher() { + @Override + protected void matchInternal(MockClientHttpRequest request) throws Exception { + xpathHelper.assertBoolean(request.getBodyAsString(), value); + } + }; + } + + + /** + * Abstract base class for XPath {@link RequestMatcher}'s. + */ + private abstract static class AbstractXpathRequestMatcher implements RequestMatcher { + + public final void match(ClientHttpRequest request) throws IOException, AssertionError { + try { + MockClientHttpRequest mockRequest = (MockClientHttpRequest) request; + matchInternal(mockRequest); + } + catch (Exception e) { + throw new AssertionError("Failed to parse XML request content: " + e.getMessage()); + } + } + + protected abstract void matchInternal(MockClientHttpRequest request) throws Exception; + + } + +} diff --git a/src/main/java/org/springframework/test/web/client/match/package-info.java b/src/main/java/org/springframework/test/web/client/match/package-info.java new file mode 100644 index 0000000..69b5bb5 --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/match/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2011-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. + */ + +/** + * Contains built-in {@link org.springframework.test.web.client.RequestMatcher} + * implementations. Use + * {@link org.springframework.test.web.client.match.RequestMatchers} + * to gain access to instances of those implementations. + */ +package org.springframework.test.web.client.match; diff --git a/src/main/java/org/springframework/test/web/client/package-info.java b/src/main/java/org/springframework/test/web/client/package-info.java new file mode 100644 index 0000000..c331fcc --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2011-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. + */ + +/** + * Contains client-side REST testing support. + * @see org.springframework.test.web.client.MockRestServiceServer + */ +package org.springframework.test.web.client; diff --git a/src/main/java/org/springframework/test/web/client/response/DefaultResponseCreator.java b/src/main/java/org/springframework/test/web/client/response/DefaultResponseCreator.java new file mode 100644 index 0000000..9e0eeee --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/response/DefaultResponseCreator.java @@ -0,0 +1,131 @@ +/* + * 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.client.response; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URI; + +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.mock.http.client.MockClientHttpResponse; +import org.springframework.test.web.client.ResponseCreator; +import org.springframework.util.Assert; + +/** + * A {@code ResponseCreator} with builder-style methods for adding response details. + * + * @author Rossen Stoyanchev + */ +public class DefaultResponseCreator implements ResponseCreator { + + private byte[] content; + + private Resource contentResource; + + private final HttpHeaders headers = new HttpHeaders(); + + private HttpStatus statusCode; + + + /** + * Protected constructor. + * Use static factory methods in {@link ResponseCreators}. + */ + protected DefaultResponseCreator(HttpStatus statusCode) { + Assert.notNull(statusCode); + this.statusCode = statusCode; + } + + public ClientHttpResponse createResponse(ClientHttpRequest request) throws IOException { + MockClientHttpResponse response; + if (this.contentResource != null ){ + InputStream stream = this.contentResource.getInputStream(); + response = new MockClientHttpResponse(stream, this.statusCode); + } + else { + response = new MockClientHttpResponse(this.content, this.statusCode); + } + response.getHeaders().putAll(this.headers); + return response; + } + + /** + * Set the body as a UTF-8 String. + */ + public DefaultResponseCreator body(String content) { + try { + this.content = content.getBytes("UTF-8"); + } + catch (UnsupportedEncodingException e) { + // should not happen, UTF-8 is always supported + throw new IllegalStateException(e); + } + return this; + } + + /** + * Set the body as a byte array. + */ + public DefaultResponseCreator body(byte[] content) { + this.content = content; + return this; + } + + /** + * Set the body as a {@link Resource}. + */ + public DefaultResponseCreator body(Resource resource) { + this.contentResource = resource; + return this; + } + + /** + * Set the {@code Content-Type} header. + */ + public DefaultResponseCreator contentType(MediaType mediaType) { + if (mediaType != null) { + this.headers.setContentType(mediaType); + } + return this; + } + + /** + * Set the {@code Location} header. + */ + public DefaultResponseCreator location(URI location) { + this.headers.setLocation(location); + return this; + } + + /** + * Copy all given headers. + */ + public DefaultResponseCreator headers(HttpHeaders headers) { + for (String headerName : headers.keySet()) { + for (String headerValue : headers.get(headerName)) { + this.headers.add(headerName, headerValue); + } + } + return this; + } + +} diff --git a/src/main/java/org/springframework/test/web/client/response/ResponseCreators.java b/src/main/java/org/springframework/test/web/client/response/ResponseCreators.java new file mode 100644 index 0000000..4edac3d --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/response/ResponseCreators.java @@ -0,0 +1,190 @@ +/* + * Copyright 2011-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.client.response; + +import java.io.IOException; +import java.net.URI; + +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.mock.http.client.MockClientHttpResponse; +import org.springframework.test.web.client.ResponseCreator; + +/** + * Static factory methods for obtaining a {@link ResponseCreator} instance. + * + * Eclipse users: consider adding this class as a Java editor + * favorite. To navigate, open the Preferences and type "favorites". + * + * @author Rossen Stoyanchev + */ +public abstract class ResponseCreators { + + /** + * Private class constructor. + */ + private ResponseCreators() { + } + + /** + * {@code ResponseCreator} for a 200 response (OK). + */ + public static DefaultResponseCreator withSuccess() { + return new DefaultResponseCreator(HttpStatus.OK); + } + + /** + * {@code ResponseCreator} for a 200 response (OK) with String body. + * @param body the response body, a "UTF-8" string + * @param mediaType the type of the content, may be {@code null} + */ + public static DefaultResponseCreator withSuccess(String body, MediaType mediaType) { + return new DefaultResponseCreator(HttpStatus.OK).body(body).contentType(mediaType); + } + + /** + * {@code ResponseCreator} for a 200 response (OK) with byte[] body. + * @param body the response body + * @param mediaType the type of the content, may be {@code null} + */ + public static DefaultResponseCreator withSuccess(byte[] body, MediaType contentType) { + return new DefaultResponseCreator(HttpStatus.OK).body(body).contentType(contentType); + } + + /** + * {@code ResponseCreator} for a 200 response (OK) content with {@link Resource}-based body. + * @param body the response body + * @param mediaType the type of the content, may be {@code null} + */ + public static DefaultResponseCreator withSuccess(Resource body, MediaType contentType) { + return new DefaultResponseCreator(HttpStatus.OK).body(body).contentType(contentType); + } + + /** + * {@code ResponseCreator} for a 201 response (CREATED) with a 'Location' header. + * @param location the value for the {@code Location} header + */ + public static DefaultResponseCreator withCreatedEntity(URI location) { + return new DefaultResponseCreator(HttpStatus.CREATED).location(location); + } + + /** + * {@code ResponseCreator} for a 204 response (NO_CONTENT). + */ + public static DefaultResponseCreator withNoContent() { + return new DefaultResponseCreator(HttpStatus.NO_CONTENT); + } + + /** + * {@code ResponseCreator} for a 400 response (BAD_REQUEST). + */ + public static DefaultResponseCreator withBadRequest() { + return new DefaultResponseCreator(HttpStatus.BAD_REQUEST); + } + + /** + * {@code ResponseCreator} for a 401 response (UNAUTHORIZED). + */ + public static DefaultResponseCreator withUnauthorizedRequest() { + return new DefaultResponseCreator(HttpStatus.UNAUTHORIZED); + } + + /** + * {@code ResponseCreator} for a 500 response (SERVER_ERROR). + */ + public static DefaultResponseCreator withServerError() { + return new DefaultResponseCreator(HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * {@code ResponseCreator} with a specific HTTP status. + * @param status the response status + */ + public static DefaultResponseCreator withStatus(HttpStatus status) { + return new DefaultResponseCreator(status); + } + + /** + * Respond with a given body, headers, status code, and status text. + * + * @param body the body of the response "UTF-8" encoded + * @param headers the response headers + * @param statusCode the response status code + * @param statusText the response status text + * + * @deprecated in favor of methods returning DefaultResponseCreator + */ + public static ResponseCreator withResponse(final String body, final HttpHeaders headers, + final HttpStatus statusCode, final String statusText) { + + return new ResponseCreator() { + public MockClientHttpResponse createResponse(ClientHttpRequest request) throws IOException { + MockClientHttpResponse response = new MockClientHttpResponse(body.getBytes("UTF-8"), statusCode); + response.getHeaders().putAll(headers); + return response; + } + }; + } + + /** + * Respond with the given body, headers, and a status code of 200 (OK). + * + * @param body the body of the response "UTF-8" encoded + * @param headers the response headers + * + * @deprecated in favor of methods 'withXyz' in this class returning DefaultResponseCreator + */ + public static ResponseCreator withResponse(String body, HttpHeaders headers) { + return withResponse(body, headers, HttpStatus.OK, ""); + } + + /** + * Respond with a given body, headers, status code, and text. + * + * @param body a {@link Resource} containing the body of the response + * @param headers the response headers + * @param statusCode the response status code + * @param statusText the response status text + * + * @deprecated in favor of methods 'withXyz' in this class returning DefaultResponseCreator + */ + public static ResponseCreator withResponse(final Resource body, final HttpHeaders headers, + final HttpStatus statusCode, String statusText) { + + return new ResponseCreator() { + public MockClientHttpResponse createResponse(ClientHttpRequest request) throws IOException { + MockClientHttpResponse response = new MockClientHttpResponse(body.getInputStream(), statusCode); + response.getHeaders().putAll(headers); + return response; + } + }; + } + + /** + * Respond with the given body, headers, and a status code of 200 (OK). + * @param body the body of the response + * @param headers the response headers + * + * @deprecated in favor of methods 'withXyz' in this class returning DefaultResponseCreator + */ + public static ResponseCreator withResponse(final Resource body, final HttpHeaders headers) { + return withResponse(body, headers, HttpStatus.OK, ""); + } + +} diff --git a/src/main/java/org/springframework/test/web/client/response/package-info.java b/src/main/java/org/springframework/test/web/client/response/package-info.java new file mode 100644 index 0000000..e09bd76 --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/response/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2011-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. + */ + +/** + * Contains built-in {@link org.springframework.test.web.client.ResponseCreator} + * implementations. Use + * {@link org.springframework.test.web.client.response.ResponseCreators} + * to gain access to instances of those implementations. + */ +package org.springframework.test.web.client.response; diff --git a/src/main/java/org/springframework/test/web/server/AbstractMockMvcBuilder.java b/src/main/java/org/springframework/test/web/server/AbstractMockMvcBuilder.java deleted file mode 100644 index e417c22..0000000 --- a/src/main/java/org/springframework/test/web/server/AbstractMockMvcBuilder.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2002-2011 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.server; - -import java.util.Collections; -import java.util.List; - -import javax.servlet.ServletContext; - -import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.servlet.HandlerAdapter; -import org.springframework.web.servlet.HandlerExceptionResolver; -import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.servlet.LocaleResolver; -import org.springframework.web.servlet.RequestToViewNameTranslator; -import org.springframework.web.servlet.ViewResolver; - -/** - * A base class that supports assembling an {@link MvcSetup} to build a {@link MockMvc} instance. - * - */ -public abstract class AbstractMockMvcBuilder { - - private WebApplicationContext applicationContext; - - private List
handlerMappings; - - private List handlerAdapters; - - private List exceptionResolvers; - - private List viewResolvers; - - private RequestToViewNameTranslator viewNameTranslator; - - private LocaleResolver localeResolver; - - public final MockMvc build() { - - applicationContext = initApplicationContext(); - ServletContext servletContext = applicationContext.getServletContext(); - - String name = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE; - servletContext.setAttribute(name, applicationContext); - - handlerMappings = Collections.unmodifiableList(initHandlerMappings()); - handlerAdapters = Collections.unmodifiableList(initHandlerAdapters()); - exceptionResolvers = Collections.unmodifiableList(initHandlerExceptionResolvers()); - viewResolvers = Collections.unmodifiableList(initViewResolvers()); - viewNameTranslator = initViewNameTranslator(); - localeResolver = initLocaleResolver(); - - MvcSetup mvcSetup = createMvcSetup(); - MockDispatcher mockDispatcher = new MockDispatcher(mvcSetup); - - return new MockMvc(servletContext, mockDispatcher); - } - - protected abstract WebApplicationContext initApplicationContext(); - - protected abstract List extends HandlerMapping> initHandlerMappings(); - - protected abstract List extends HandlerAdapter> initHandlerAdapters(); - - protected abstract List extends HandlerExceptionResolver> initHandlerExceptionResolvers(); - - protected abstract List extends ViewResolver> initViewResolvers(); - - protected abstract RequestToViewNameTranslator initViewNameTranslator(); - - protected abstract LocaleResolver initLocaleResolver(); - - private MvcSetup createMvcSetup() { - - return new MvcSetup() { - - public List getHandlerMappings() { - return handlerMappings; - } - - public List getHandlerAdapters() { - return handlerAdapters; - } - - public List getViewResolvers() { - return viewResolvers; - } - - public List getExceptionResolvers() { - return exceptionResolvers; - } - - public RequestToViewNameTranslator getViewNameTranslator() { - return viewNameTranslator; - } - - public LocaleResolver getLocaleResolver() { - return localeResolver; - } - }; - } -} diff --git a/src/main/java/org/springframework/test/web/server/DefaultMockHttpServletRequestBuilder.java b/src/main/java/org/springframework/test/web/server/DefaultMockHttpServletRequestBuilder.java deleted file mode 100644 index 0cfdb22..0000000 --- a/src/main/java/org/springframework/test/web/server/DefaultMockHttpServletRequestBuilder.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright 2002-2011 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.server; - -import java.net.URI; -import java.security.Principal; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.Locale; -import java.util.Map; -import javax.servlet.ServletContext; -import javax.servlet.http.Cookie; - -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.util.Assert; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -/** - * A command class to build and execute a request. Use methods on {@link MockMvc} to obtain a new {@link - * DefaultMockHttpServletRequestBuilder} instance. - */ -public class DefaultMockHttpServletRequestBuilder implements MockHttpServletRequestBuilder { - - private final URI uri; - - private final HttpMethod method; - - private final MultiValueMap parameters = new LinkedMultiValueMap (); - - private final MultiValueMap headers = new LinkedMultiValueMap (); - - private String contentType; - - private byte[] requestBody; - - private Cookie[] cookies; - - private Locale locale; - - private String characterEncoding; - - private final Map attributes = new LinkedHashMap (); - - private final Map sessionAttributes = new LinkedHashMap (); - - private Principal principal; - - /** Use methods on {@link MockMvc} to obtain a new instance. */ - DefaultMockHttpServletRequestBuilder(URI uri, HttpMethod method) { - this.uri = uri; - this.method = method; - } - - public DefaultMockHttpServletRequestBuilder param(String name, String value, String... values) { - addToMultiValueMap(parameters, name, value, values); - return this; - } - - public DefaultMockHttpServletRequestBuilder accept(MediaType mediaType, MediaType... mediaTypes) { - addToMultiValueMap(headers, "Accept", mediaType, mediaTypes); - return this; - } - - public DefaultMockHttpServletRequestBuilder contentType(MediaType mediaType) { - Assert.notNull(mediaType, "'mediaType' must not be null"); - this.contentType = mediaType.toString(); - headers.set("Content-Type", mediaType); - return this; - } - - public DefaultMockHttpServletRequestBuilder body(byte[] requestBody) { - this.requestBody = requestBody; - return this; - } - - public DefaultMockHttpServletRequestBuilder header(String name, Object value, Object... values) { - addToMultiValueMap(headers, name, value, values); - return this; - } - - public DefaultMockHttpServletRequestBuilder cookie(Cookie cookie, Cookie... cookies) { - Assert.notNull(cookie, "'cookie' must not be null"); - if (cookies == null) { - this.cookies = new Cookie[]{cookie}; - } - else { - this.cookies = new Cookie[1 + cookies.length]; - this.cookies[0] = cookie; - System.arraycopy(cookies, 0, this.cookies, 1, cookies.length); - } - return this; - } - - public DefaultMockHttpServletRequestBuilder locale(Locale locale) { - this.locale = locale; - return this; - } - - public DefaultMockHttpServletRequestBuilder characterEncoding(String characterEncoding) { - this.characterEncoding = characterEncoding; - return this; - } - - public DefaultMockHttpServletRequestBuilder requestAttr(String name, Object value) { - Assert.hasLength(name, "'name' must not be empty"); - Assert.notNull(value, "'value' must not be null"); - attributes.put(name, value); - return this; - } - - public DefaultMockHttpServletRequestBuilder sessionAttr(String name, Object value) { - Assert.hasLength(name, "'name' must not be empty"); - Assert.notNull(value, "'value' must not be null"); - sessionAttributes.put(name, value); - return this; - } - - public DefaultMockHttpServletRequestBuilder principal(Principal principal) { - Assert.notNull(principal, "'principal' must not be null"); - this.principal = principal; - return this; - } - - public MockHttpServletRequest buildRequest(ServletContext servletContext) { - - MockHttpServletRequest request = createServletRequest(servletContext); - - request.setMethod(method.name()); - request.setRequestURI(uri.toString()); - - for (String name : parameters.keySet()) { - for (String value : parameters.get(name)) { - request.addParameter(name, value); - } - } - for (String name : headers.keySet()) { - for (Object value : headers.get(name)) { - request.addHeader(name, value); - } - } - for (String name : attributes.keySet()) { - request.setAttribute(name, attributes.get(name)); - } - for (String name : sessionAttributes.keySet()) { - request.getSession().setAttribute(name, sessionAttributes.get(name)); - } - - request.setContentType(contentType); - request.setContent(requestBody); - request.setCookies(cookies); - request.setCharacterEncoding(characterEncoding); - request.setUserPrincipal(principal); - - if (locale != null) { - request.addPreferredLocale(locale); - } - - return request; - } - - /** - * Creates a new {@link MockHttpServletRequest} based on the given {@link ServletContext}. Can be overridden in - * subclasses. - * - * @param servletContext the servlet context to use - * @return the created mock request - */ - protected MockHttpServletRequest createServletRequest(ServletContext servletContext) { - return new MockHttpServletRequest(servletContext); - } - - private static void addToMultiValueMap(MultiValueMap map, String name, T value, T[] values) { - Assert.hasLength(name, "'name' must not be empty"); - Assert.notNull(value, "'value' must not be null"); - map.add(name, value); - if (values != null) { - map.get(name).addAll(Arrays.asList(values)); - } - } - -} diff --git a/src/main/java/org/springframework/test/web/server/DefaultMvcResult.java b/src/main/java/org/springframework/test/web/server/DefaultMvcResult.java new file mode 100644 index 0000000..431377b --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/DefaultMvcResult.java @@ -0,0 +1,98 @@ +/* + * Copyright 2002-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.server; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.servlet.FlashMap; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.support.RequestContextUtils; + +/** + * A simple implementation of {@link MvcResult} with setters. + * + * @author Rossen Stoyanchev + * @author Rob Winch + */ +class DefaultMvcResult implements MvcResult { + + private final MockHttpServletRequest mockRequest; + + private final MockHttpServletResponse mockResponse; + + private Object handler; + + private HandlerInterceptor[] interceptors; + + private ModelAndView modelAndView; + + private Exception resolvedException; + + + /** + * Create a new instance with the given request and response. + */ + public DefaultMvcResult(MockHttpServletRequest request, MockHttpServletResponse response) { + this.mockRequest = request; + this.mockResponse = response; + } + + public MockHttpServletResponse getResponse() { + return mockResponse; + } + + public MockHttpServletRequest getRequest() { + return mockRequest; + } + + public Object getHandler() { + return this.handler; + } + + public void setHandler(Object handler) { + this.handler = handler; + } + + public HandlerInterceptor[] getInterceptors() { + return this.interceptors; + } + + public void setInterceptors(HandlerInterceptor[] interceptors) { + this.interceptors = interceptors; + } + + public Exception getResolvedException() { + return this.resolvedException; + } + + public void setResolvedException(Exception resolvedException) { + this.resolvedException = resolvedException; + } + + public ModelAndView getModelAndView() { + return this.modelAndView; + } + + public void setModelAndView(ModelAndView mav) { + this.modelAndView = mav; + } + + public FlashMap getFlashMap() { + return RequestContextUtils.getOutputFlashMap(mockRequest); + } + +} diff --git a/src/main/java/org/springframework/test/web/server/MockDispatcher.java b/src/main/java/org/springframework/test/web/server/MockDispatcher.java deleted file mode 100644 index 23b83ba..0000000 --- a/src/main/java/org/springframework/test/web/server/MockDispatcher.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright 2002-2011 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.server; - -import static org.springframework.test.web.AssertionErrors.fail; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.util.Assert; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; -import org.springframework.web.servlet.DispatcherServlet; -import org.springframework.web.servlet.HandlerAdapter; -import org.springframework.web.servlet.HandlerExceptionResolver; -import org.springframework.web.servlet.HandlerExecutionChain; -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.View; -import org.springframework.web.servlet.ViewResolver; - -/** - * A more "lightweight" alternative to the {@link DispatcherServlet} re-purposed for testing Spring MVC applications - * outside of a Servlet container environment in mind. Mimics the essential functionality of the DispatcherServlet - * but does not always behave in identical ways. For example invoking afterCompletion() on a HandlerInterceptor is - * not essential for integration testing since the same method can be unit tested. - * - * Unlike the DispatcherServlet, the {@link MockDispatcher} is stateful. It records contextual information during - * each invocation such as the request and the response, the mapped handler and handler interceptors, and the resulting - * ModelAndView. The recorded information may then be matched against application-specific expectations as defined by - * {@link MvcResultActions}. Previously recorded context is cleared at the start of every dispatch invocation. - * - * @NotThreadSafe - */ -public class MockDispatcher { - - private Log logger = LogFactory.getLog(getClass()); - - private final MvcSetup mvcSetup; - - private MockHttpServletRequest request; - - private MockHttpServletResponse response; - - private Object handler; - - private HandlerInterceptor[] interceptors; - - private ModelAndView mav; - - private Exception handlerException; - - /** - * Create a {@link MockDispatcher} with the provided {@link MvcSetup}. - */ - MockDispatcher(MvcSetup setup) { - this.mvcSetup = setup; - } - - /** - * Process the request by invoking Spring MVC components in the {@link MvcSetup} provided to the constructor. - * The request may be partially processed if mapOnly is {@code true}. - * - */ - public MvcResultActions dispatch(MockHttpServletRequest request, MockHttpServletResponse response, boolean mapOnly) { - clear(); - this.request = request; - this.response = response; - - try { - RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); - doDispatch(mapOnly); - } - catch (Exception exception) { - logger.error("Unhandled exception", exception); - fail("Failed to dispatch Mock MVC request (check logs for stacktrace): " + exception); - } - finally { - RequestContextHolder.resetRequestAttributes(); - } - - return this.new ResultActionsAdapter(); - } - - private void clear() { - request = null; - response = null; - handler = null; - interceptors = null; - mav = null; - } - - private void doDispatch(boolean mapOnly) throws Exception { - - try { - initHandlerExecutionChain(); - - if (handler == null || mapOnly) { - return; - } - - List
interceptorList = (interceptors != null) ? - Arrays.asList(interceptors) : new ArrayList (); - - for (HandlerInterceptor interceptor : interceptorList) { - if (!interceptor.preHandle(request, response, handler)) { - return; - } - } - - HandlerAdapter adapter = getHandlerAdapter(); - mav = adapter.handle(request, response, handler); - updateDefaultViewName(); - - Collections.reverse(interceptorList); - for (HandlerInterceptor interceptor : interceptorList) { - interceptor.postHandle(request, response, handler, mav); - } - } - catch (Exception exception) { - processHandlerException(exception); - updateDefaultViewName(); - } - - if (mav == null) { - return; - } - - Locale locale = mvcSetup.getLocaleResolver().resolveLocale(request); - response.setLocale(locale); - - View view = resolveView(locale); - view.render(mav.getModel(), request, response); - } - - private void initHandlerExecutionChain() throws Exception { - for (HandlerMapping mapping : mvcSetup.getHandlerMappings()) { - HandlerExecutionChain chain = mapping.getHandler(request); - if (chain != null) { - handler = chain.getHandler(); - interceptors = chain.getInterceptors(); - return; - } - } - response.sendError(HttpServletResponse.SC_NOT_FOUND); - } - - private HandlerAdapter getHandlerAdapter() { - for (HandlerAdapter adapter : mvcSetup.getHandlerAdapters()) { - if (adapter.supports(handler)) { - return adapter; - } - } - throw new IllegalStateException("No adapter for handler [" + handler - + "]. Available adapters: [" + mvcSetup.getHandlerAdapters() + "]"); - } - - private void updateDefaultViewName() throws Exception { - if (mav != null && !mav.hasView()) { - String viewName = mvcSetup.getViewNameTranslator().getViewName(request); - mav.setViewName(viewName); - } - } - - private void processHandlerException(Exception exception) throws Exception { - handlerException = exception; - for (HandlerExceptionResolver resolver : mvcSetup.getExceptionResolvers()) { - mav = resolver.resolveException(request, response, handler, exception); - if (mav != null) { - mav = mav.isEmpty() ? null : mav; - return; - } - } - throw exception; - } - - private View resolveView(Locale locale) throws Exception { - if (mav.isReference()) { - for (ViewResolver viewResolver : mvcSetup.getViewResolvers()) { - View view = viewResolver.resolveViewName(mav.getViewName(), locale); - if (view != null) { - return view; - } - } - } - View view = mav.getView(); - Assert.isTrue(view != null, "Could not resolve view from ModelAndView: <" + mav + ">"); - return view; - } - - private class ResultActionsAdapter implements MvcResultActions { - - public MvcResultActions andExpect(MvcResultMatcher matcher) { - matcher.match(request, response, handler, handlerException, mav); - return this; - } - - } - -} diff --git a/src/main/java/org/springframework/test/web/server/MockFilterChain.java b/src/main/java/org/springframework/test/web/server/MockFilterChain.java new file mode 100644 index 0000000..07927ff --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/MockFilterChain.java @@ -0,0 +1,189 @@ +/* + * Copyright 2002-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.server; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.Servlet; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.springframework.mock.web.MockFilterConfig; +import org.springframework.mock.web.PassThroughFilterChain; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; + +/** + * Note: + * this class is a copy of the same class in the spring-test module of + * the Spring Framework with some additional changes. It's here temporarily + * until Spring MVC Test moves into the Spring Framework. + * + * Mock implementation of the {@link javax.servlet.FilterChain} interface. Used + * for testing the web framework; also useful for testing custom + * {@link javax.servlet.Filter} implementations. + * + *
A {@link MockFilterChain} can be configured with one or more filters and a + * Servlet to be invoked. When the chain is invoked, it invokes in turn all + * filters and the Servlet and saves the request and response. Subsequent + * invocations raise an {@link IllegalStateException} unless {@link #reset()} is + * called. + * + * @author Juergen Hoeller + * @author Rob Winch + * @author Rossen Stoyanchev + * + * @since 2.0.3 + * @see MockFilterConfig + * @see PassThroughFilterChain + */ +public class MockFilterChain implements FilterChain { + + private ServletRequest request; + + private ServletResponse response; + + private final List
filters; + + private Iterator iterator; + + + /** + * Register a single do-nothing {@link Filter} implementation. The first + * invocation saves the request and response. Subsequent invocations raise + * an {@link IllegalStateException} unless {@link #reset()} is called. + */ + public MockFilterChain() { + this.filters = Collections.emptyList(); + } + + /** + * Create a FilterChain with a Servlet. + * + * @param servlet the Servlet to invoke + * @since 3.2 + */ + public MockFilterChain(Servlet servlet) { + this.filters = initFilterList(servlet); + } + + /** + * Create a {@code FilterChain} with Filter's and a Servlet. + * + * @param servlet the {@link Servlet} to invoke in this {@link FilterChain} + * @param filters the {@link Filter}'s to invoke in this {@link FilterChain} + * @since 3.2 + */ + public MockFilterChain(Servlet servlet, Filter... filters) { + Assert.notNull(filters, "filters cannot be null"); + Assert.noNullElements(filters, "filters cannot contain null values"); + this.filters = initFilterList(servlet, filters); + } + + private static List initFilterList(Servlet servlet, Filter... filters) { + Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet)); + return Arrays.asList(allFilters); + } + + /** + * Return the request that {@link #doFilter} has been called with. + */ + public ServletRequest getRequest() { + return this.request; + } + + /** + * Return the response that {@link #doFilter} has been called with. + */ + public ServletResponse getResponse() { + return this.response; + } + + /** + * Invoke registered {@link Filter}s and/or {@link Servlet} also saving the + * request and response. + */ + public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { + Assert.notNull(request, "Request must not be null"); + Assert.notNull(response, "Response must not be null"); + + if (this.request != null) { + throw new IllegalStateException("This FilterChain has already been called!"); + } + + if (this.iterator == null) { + this.iterator = this.filters.iterator(); + } + + if (this.iterator.hasNext()) { + Filter nextFilter = this.iterator.next(); + nextFilter.doFilter(request, response, this); + } + + this.request = request; + this.response = response; + } + + /** + * Reset the {@link MockFilterChain} allowing it to be invoked again. + */ + public void reset() { + this.request = null; + this.response = null; + this.iterator = null; + } + + + /** + * A filter that simply delegates to a Servlet. + */ + private static class ServletFilterProxy implements Filter { + + private final Servlet delegateServlet; + + private ServletFilterProxy(Servlet servlet) { + Assert.notNull(servlet, "servlet cannot be null"); + this.delegateServlet = servlet; + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + this.delegateServlet.service(request, response); + } + + public void init(FilterConfig filterConfig) throws ServletException { + } + + public void destroy() { + } + + @Override + public String toString() { + return this.delegateServlet.toString(); + } + } + +} diff --git a/src/main/java/org/springframework/test/web/server/MockHttpServletRequestBuilder.java b/src/main/java/org/springframework/test/web/server/MockHttpServletRequestBuilder.java deleted file mode 100644 index 2b0eeb8..0000000 --- a/src/main/java/org/springframework/test/web/server/MockHttpServletRequestBuilder.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.springframework.test.web.server; - -import javax.servlet.ServletContext; - -import org.springframework.mock.web.MockHttpServletRequest; - -/** @author Arjen Poutsma */ -public interface MockHttpServletRequestBuilder { - - MockHttpServletRequest buildRequest(ServletContext servletContext); - -} diff --git a/src/main/java/org/springframework/test/web/server/MockHttpServletRequestBuilders.java b/src/main/java/org/springframework/test/web/server/MockHttpServletRequestBuilders.java deleted file mode 100644 index 4a72fd8..0000000 --- a/src/main/java/org/springframework/test/web/server/MockHttpServletRequestBuilders.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.springframework.test.web.server; - -import java.net.URI; - -import org.springframework.http.HttpMethod; -import org.springframework.web.util.UriTemplate; - -/** @author Arjen Poutsma */ -public abstract class MockHttpServletRequestBuilders { - - private MockHttpServletRequestBuilders() { - } - - public static DefaultMockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables) { - return request(HttpMethod.GET, urlTemplate, urlVariables); - } - - public static DefaultMockHttpServletRequestBuilder post(String urlTemplate, Object... urlVariables) { - return request(HttpMethod.POST, urlTemplate, urlVariables); - } - - public static DefaultMockHttpServletRequestBuilder put(String urlTemplate, Object... urlVariables) { - return request(HttpMethod.PUT, urlTemplate, urlVariables); - } - - public static DefaultMockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVariables) { - return request(HttpMethod.DELETE, urlTemplate, urlVariables); - } - - public static MultipartMockHttpServletRequestBuilder fileUpload(String urlTemplate, Object... urlVariables) { - URI url = expandUrl(urlTemplate, urlVariables); - return new MultipartMockHttpServletRequestBuilder(url); - } - - public static DefaultMockHttpServletRequestBuilder request(HttpMethod method, String urlTemplate, Object... urlVariables) { - URI url = expandUrl(urlTemplate, urlVariables); - return new DefaultMockHttpServletRequestBuilder(url, method); - } - - private static URI expandUrl(String urlTemplate, Object[] urlVariables) { - UriTemplate uriTemplate = new UriTemplate(urlTemplate); - return uriTemplate.expand(urlVariables); - } - - -} diff --git a/src/main/java/org/springframework/test/web/server/MockMvc.java b/src/main/java/org/springframework/test/web/server/MockMvc.java index 70941f7..26b30ce 100644 --- a/src/main/java/org/springframework/test/web/server/MockMvc.java +++ b/src/main/java/org/springframework/test/web/server/MockMvc.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2011-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. @@ -16,57 +16,150 @@ package org.springframework.test.web.server; +import java.util.ArrayList; +import java.util.List; + import javax.servlet.ServletContext; +import org.springframework.beans.Mergeable; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.util.Assert; + +/** + * Main entry point for server-side Spring MVC test support. + * + * Below is an example: + * + *
+ * static imports: + * MockMvcBuilders.*, MockMvcRequestBuilders.*, MockMvcResultMatchers.* + * + * MockMvc mockMvc = + * annotationConfigMvcSetup(TestConfiguration.class) + * .configureWarRootDir("src/main/webapp", false).build() + * + * mockMvc.perform(get("/form")) + * .andExpect(status().isOk()) + * .andExpect(content().mimeType("text/plain")) + * .andExpect(forwardedUrl("/WEB-INF/layouts/main.jsp")); + *+ * + * @author Rossen Stoyanchev + * @author Rob Winch + */ +public final class MockMvc { + + static String MVC_RESULT_ATTRIBUTE = MockMvc.class.getName().concat(".MVC_RESULT_ATTRIBUTE"); + + private final MockFilterChain filterChain; + + private final ServletContext servletContext; + + private RequestBuilder defaultRequestBuilder; + + private ListdefaultResultMatchers = new ArrayList (); + + private List defaultResultHandlers = new ArrayList (); + + + /** + * Private constructor, not for direct instantiation. + * @see org.springframework.test.web.server.setup.MockMvcBuilders + */ + MockMvc(MockFilterChain filterChain, ServletContext servletContext) { + Assert.notNull(servletContext, "A ServletContext is required"); + Assert.notNull(filterChain, "A MockFilterChain is required"); + + this.filterChain = filterChain; + this.servletContext = servletContext; + } + + /** + * A default request builder merged into every performed request. + * @see org.springframework.test.web.server.setup.AbstractMockMvcBuilder#defaultRequest(RequestBuilder) + */ + void setDefaultRequest(RequestBuilder requestBuilder) { + this.defaultRequestBuilder = requestBuilder; + } + + /** + * Expectations to assert after every performed request. + * @see org.springframework.test.web.server.setup.AbstractMockMvcBuilder#alwaysExpect(ResultMatcher) + */ + void setGlobalResultMatchers(List resultMatchers) { + Assert.notNull(resultMatchers, "resultMatchers is required"); + this.defaultResultMatchers = resultMatchers; + } + + /** + * General actions to apply after every performed request. + * @see org.springframework.test.web.server.setup.AbstractMockMvcBuilder#alwaysDo(ResultHandler) + */ + void setGlobalResultHandlers(List resultHandlers) { + Assert.notNull(resultHandlers, "resultHandlers is required"); + this.defaultResultHandlers = resultHandlers; + } + + /** + * Perform a request and return a type that allows chaining further + * actions, such as asserting expectations, on the result. + * + * @param requestBuilder used to prepare the request to execute; + * see static factory methods in + * {@link org.springframework.test.web.server.request.MockMvcRequestBuilders} + * + * @return an instance of {@link ResultActions}; never {@code null} + * + * @see org.springframework.test.web.server.request.MockMvcRequestBuilders + * @see org.springframework.test.web.server.result.MockMvcResultMatchers + */ + public ResultActions perform(RequestBuilder requestBuilder) throws Exception { + + if (this.defaultRequestBuilder != null) { + if (requestBuilder instanceof Mergeable) { + requestBuilder = (RequestBuilder) ((Mergeable) requestBuilder).merge(this.defaultRequestBuilder); + } + } + + MockHttpServletRequest request = requestBuilder.buildRequest(this.servletContext); + MockHttpServletResponse response = new MockHttpServletResponse(); + + final MvcResult mvcResult = new DefaultMvcResult(request, response); + request.setAttribute(MVC_RESULT_ATTRIBUTE, mvcResult); + + this.filterChain.reset(); + this.filterChain.doFilter(request, response); + + applyDefaultResultActions(mvcResult); + + return new ResultActions() { + + public ResultActions andExpect(ResultMatcher matcher) throws Exception { + matcher.match(mvcResult); + return this; + } + + public ResultActions andDo(ResultHandler printer) throws Exception { + printer.handle(mvcResult); + return this; + } + + public MvcResult andReturn() { + return mvcResult; + } + }; + } + + private void applyDefaultResultActions(MvcResult mvcResult) throws Exception { + + for (ResultMatcher matcher : this.defaultResultMatchers) { + matcher.match(mvcResult); + } -/** Main entry point for server-side Spring MVC test support. */ -public class MockMvc { - - private final ServletContext servletContext; - - private final MockDispatcher mockDispatcher; - - private boolean mapOnly; - - /** To create a {@link MockMvc} instance see methods in {@code MockMvcBuilders}. */ - MockMvc(ServletContext servletContext, MockDispatcher mockDispatcher) { - this.servletContext = servletContext; - this.mockDispatcher = mockDispatcher; - } - - /** - * Enables a mode in which requests are mapped to a handler without actually invoking it afterwards. Allows verifying - * the handler or handler method a request is mapped to. - */ - public MockMvc setMapOnly(boolean enable) { - this.mapOnly = enable; - return this; - } - - /* - public static MockMvc createFromApplicationContext(ApplicationContext applicationContext) { - // TODO - return null; - } - - public static MockMvc createFromWebXml(String webXmlFileName) { - // TODO - return null; - } - */ - - // Perform - - public MvcResultActions perform(MockHttpServletRequestBuilder requestBuilder) { - MockHttpServletRequest request = requestBuilder.buildRequest(servletContext); - MockHttpServletResponse response = new MockHttpServletResponse(); - return execute(request, response); - } - - protected MvcResultActions execute(MockHttpServletRequest request, MockHttpServletResponse response) { - return mockDispatcher.dispatch(request, response, mapOnly); - } + for (ResultHandler handler : this.defaultResultHandlers) { + handler.handle(mvcResult); + } + } } diff --git a/src/main/java/org/springframework/test/web/server/MvcResultActions.java b/src/main/java/org/springframework/test/web/server/MockMvcBuilder.java similarity index 60% rename from src/main/java/org/springframework/test/web/server/MvcResultActions.java rename to src/main/java/org/springframework/test/web/server/MockMvcBuilder.java index 817445e..fe3826a 100644 --- a/src/main/java/org/springframework/test/web/server/MvcResultActions.java +++ b/src/main/java/org/springframework/test/web/server/MockMvcBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2011 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. @@ -16,17 +16,19 @@ package org.springframework.test.web.server; - - /** - * Allows setting up actions against the results of a processed request via method chaining. + * Builds a {@link MockMvc}. + * + * See static, factory methods in + * {@code org.springframework.test.web.server.setup.MockMvcBuilders}. + * + * @author Rossen Stoyanchev */ -public interface MvcResultActions { +public interface MockMvcBuilder { /** - * Define an expectation about the results from a processed request. - * See methods in {@code MvcResultMatchers} for most commonly used {@link MvcResultMatcher}s. + * Build a {@link MockMvc} instance. */ - MvcResultActions andExpect(MvcResultMatcher matcher); + MockMvc build(); } \ No newline at end of file diff --git a/src/main/java/org/springframework/test/web/server/MockMvcBuilderSupport.java b/src/main/java/org/springframework/test/web/server/MockMvcBuilderSupport.java new file mode 100644 index 0000000..db35180 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/MockMvcBuilderSupport.java @@ -0,0 +1,76 @@ +/* + * Copyright 2011-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.server; + +import java.util.List; + +import javax.servlet.Filter; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.springframework.core.NestedRuntimeException; +import org.springframework.mock.web.MockServletConfig; +import org.springframework.web.context.WebApplicationContext; + +/** + * Base class for MockMvc builder implementations, providing the capability to + * create a {@link MockMvc} instance. + * + *
{@link org.springframework.test.web.server.setup.AbstractMockMvcBuilder}, + * which derives from this class, provides a concrete {@code build} method, + * and delegates to abstract methods to obtain a {@link WebApplicationContext}. + * + * @author Rossen Stoyanchev + * @author Rob Winch + */ +public abstract class MockMvcBuilderSupport { + + protected MockMvc createMockMvc(Filter[] filters, MockServletConfig servletConfig, + WebApplicationContext webAppContext, RequestBuilder defaultRequestBuilder, + List
globalResultMatchers, List globalResultHandlers) { + + ServletContext servletContext = webAppContext.getServletContext(); + + TestDispatcherServlet dispatcherServlet = new TestDispatcherServlet(webAppContext); + try { + dispatcherServlet.init(servletConfig); + } + catch (ServletException ex) { + // should never happen.. + throw new MockMvcBuildException("Failed to initialize TestDispatcherServlet", ex); + } + + MockFilterChain filterChain = new MockFilterChain(dispatcherServlet, filters); + + MockMvc mockMvc = new MockMvc(filterChain, servletContext); + mockMvc.setDefaultRequest(defaultRequestBuilder); + mockMvc.setGlobalResultMatchers(globalResultMatchers); + mockMvc.setGlobalResultHandlers(globalResultHandlers); + + return mockMvc; + } + + + @SuppressWarnings("serial") + private static class MockMvcBuildException extends NestedRuntimeException { + + public MockMvcBuildException(String msg, Throwable cause) { + super(msg, cause); + } + } + +} diff --git a/src/main/java/org/springframework/test/web/server/MultipartMockHttpServletRequestBuilder.java b/src/main/java/org/springframework/test/web/server/MultipartMockHttpServletRequestBuilder.java deleted file mode 100644 index d5e4ad3..0000000 --- a/src/main/java/org/springframework/test/web/server/MultipartMockHttpServletRequestBuilder.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2002-2011 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.server; - -import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import javax.servlet.ServletContext; - -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.mock.web.MockMultipartHttpServletRequest; - -/** - * Implementation of the {@link MockHttpServletRequestBuilder} interface that provides access to multipart requests. - * - */ -public class MultipartMockHttpServletRequestBuilder extends DefaultMockHttpServletRequestBuilder { - - private final List files = new ArrayList (); - - MultipartMockHttpServletRequestBuilder(URI uri) { - super(uri, HttpMethod.POST); - super.contentType(MediaType.MULTIPART_FORM_DATA); - } - - /** - * Create a new MockMultipartFile with the given content. - * - * @param name the name of the file - * @param content the content of the file - */ - public MultipartMockHttpServletRequestBuilder file(String name, byte[] content) { - files.add(new MockMultipartFile(name, content)); - return this; - } - - /** - * Adds the given MockMultipartFile. - * - * @param file the multipart file - */ - public MultipartMockHttpServletRequestBuilder file(MockMultipartFile file) { - files.add(file); - return this; - } - - @Override - protected final MockHttpServletRequest createServletRequest(ServletContext servletContext) { - MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(); - for (MockMultipartFile file : files) { - request.addFile(file); - } - return request; - } - -} diff --git a/src/main/java/org/springframework/test/web/server/MvcResult.java b/src/main/java/org/springframework/test/web/server/MvcResult.java new file mode 100644 index 0000000..83db98f --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/MvcResult.java @@ -0,0 +1,77 @@ +/* + * Copyright 2011-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.server; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.servlet.FlashMap; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +/** + * Provides access to the result of an executed request. + * + * @author Rossen Stoyanchev + */ +public interface MvcResult { + + /** + * Return the performed request. + * @return the request, never {@code null} + */ + MockHttpServletRequest getRequest(); + + /** + * Return the resulting response. + * @return the response, never {@code null} + */ + MockHttpServletResponse getResponse(); + + /** + * Return the executed handler. + * @return the handler, possibly {@code null} if none were executed + */ + Object getHandler(); + + /** + * Return interceptors around the handler. + * @return interceptors, or {@code null} if none were selected + */ + HandlerInterceptor[] getInterceptors(); + + /** + * Return the {@code ModelAndView} prepared by the handler. + * @return a {@code ModelAndView}, or {@code null} + */ + ModelAndView getModelAndView(); + + /** + * Return any exception raised by a handler and successfully resolved + * through a {@link HandlerExceptionResolver}. + * + * @return an exception, possibly {@code null} + */ + Exception getResolvedException(); + + /** + * Return the "output" flash attributes saved during request processing. + * @return the {@code FlashMap}, possibly empty + */ + FlashMap getFlashMap(); + +} diff --git a/src/main/java/org/springframework/test/web/server/MvcSetup.java b/src/main/java/org/springframework/test/web/server/MvcSetup.java deleted file mode 100644 index 18afc9f..0000000 --- a/src/main/java/org/springframework/test/web/server/MvcSetup.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2002-2011 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.server; - -import java.util.List; - -import org.springframework.web.servlet.HandlerAdapter; -import org.springframework.web.servlet.HandlerExceptionResolver; -import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.servlet.LocaleResolver; -import org.springframework.web.servlet.RequestToViewNameTranslator; -import org.springframework.web.servlet.ViewResolver; - -/** - * Provides access to Spring MVC infrastructure components. - * - */ -public interface MvcSetup { - - List getHandlerMappings(); - - List getHandlerAdapters(); - - List getExceptionResolvers(); - - List getViewResolvers(); - - RequestToViewNameTranslator getViewNameTranslator(); - - LocaleResolver getLocaleResolver(); - -} diff --git a/src/main/java/org/springframework/test/web/server/RequestBuilder.java b/src/main/java/org/springframework/test/web/server/RequestBuilder.java new file mode 100644 index 0000000..7a48755 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/RequestBuilder.java @@ -0,0 +1,26 @@ +package org.springframework.test.web.server; + +import javax.servlet.ServletContext; + +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * Builds a {@link MockHttpServletRequest}. + * + * See static, factory methods in + * {@code org.springframework.test.web.server.request.MockMvcRequestBuilders}. + * + * @author Arjen Poutsma + * @author Rossen Stoyanchev + */ +public interface RequestBuilder { + + /** + * Build the request. + * + * @param servletContext the {@link ServletContext} to use to create the request + * @return the request + */ + MockHttpServletRequest buildRequest(ServletContext servletContext); + +} diff --git a/src/main/java/org/springframework/test/web/server/ResultActions.java b/src/main/java/org/springframework/test/web/server/ResultActions.java new file mode 100644 index 0000000..dc47614 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/ResultActions.java @@ -0,0 +1,69 @@ +/* + * Copyright 2011-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.server; + +/** + * Allows applying actions, such as expectations, on the result of an executed + * request. + * + *
See static factory methods in + * {@code org.springframework.test.web.server.result.MockMvcResultMatchers} + * {@code org.springframework.test.web.server.result.MockMvcResultHandlers} + * + * @author Rossen Stoyanchev + */ +public interface ResultActions { + + /** + * Provide an expectation. For example: + *
+ * static imports: MockMvcRequestBuilders.*, MockMvcResultMatchers.* + * + * mockMvc.perform(get("/person/1")) + * .andExpect(status().isOk()) + * .andExpect(content().mimeType(MediaType.APPLICATION_JSON)) + * .andExpect(jsonPath("$.person.name").equalTo("Jason")); + * + * mockMvc.perform(post("/form")) + * .andExpect(status().isOk()) + * .andExpect(redirectedUrl("/person/1")) + * .andExpect(model().size(1)) + * .andExpect(model().attributeExists("person")) + * .andExpect(flash().attributeCount(1)) + * .andExpect(flash().attribute("message", "success!")); + *+ */ + ResultActions andExpect(ResultMatcher matcher) throws Exception; + + /** + * Provide a general action. For example: + *+ * static imports: MockMvcRequestBuilders.*, MockMvcResultMatchers.* + * + * mockMvc.perform(get("/form")).andDo(print()); + *+ */ + ResultActions andDo(ResultHandler handler) throws Exception; + + /** + * Return the result of the executed request for direct access to the results. + * + * @return the result of the request + */ + MvcResult andReturn(); + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/test/web/server/ResultHandler.java b/src/main/java/org/springframework/test/web/server/ResultHandler.java new file mode 100644 index 0000000..96a1702 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/ResultHandler.java @@ -0,0 +1,46 @@ +/* + * Copyright 2011 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.server; + +/** + * Executes a generic action (e.g. printing debug information) on the result of + * an executed request. + * + *See static factory methods in + * {@code org.springframework.test.web.server.result.MockMvcResultHandlers}. + * + *
Example: + * + *
+ * static imports: MockMvcRequestBuilders.*, MockMvcResultHandlers.* + * + * mockMvc.perform(get("/form")).andDo(print()); + *+ * + * @author Rossen Stoyanchev + */ +public interface ResultHandler { + + /** + * Apply the action on the given result. + * + * @param result the result of the executed request + * @throws Exception if a failure occurs + */ + void handle(MvcResult result) throws Exception; + +} diff --git a/src/main/java/org/springframework/test/web/server/ResultMatcher.java b/src/main/java/org/springframework/test/web/server/ResultMatcher.java new file mode 100644 index 0000000..6559546 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/ResultMatcher.java @@ -0,0 +1,47 @@ +/* + * Copyright 2011-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.server; + +/** + * Matches the result of an executed request against some expectation. + * + *See static factory methods in + * {@code org.springframework.test.web.server.result.MockMvcResultMatchers}. + * + *
Example: + * + *
+ * static imports: MockMvcRequestBuilders.*, MockMvcResultMatchers.* + * + * mockMvc.perform(get("/form")) + * .andExpect(status().isOk()) + * .andExpect(content().mimeType(MediaType.APPLICATION_JSON)); + *+ * + * @author Rossen Stoyanchev + */ +public interface ResultMatcher { + + /** + * Assert the result of an executed request. + * + * @param mvcResult the result of the executed request + * @throws Exception if a failure occurs + */ + void match(MvcResult result) throws Exception; + +} diff --git a/src/main/java/org/springframework/test/web/server/TestDispatcherServlet.java b/src/main/java/org/springframework/test/web/server/TestDispatcherServlet.java new file mode 100644 index 0000000..5bcfef1 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/TestDispatcherServlet.java @@ -0,0 +1,84 @@ +/* + * Copyright 2011-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.server; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.HandlerExecutionChain; +import org.springframework.web.servlet.ModelAndView; + +/** + * A sub-class of {@code DispatcherServlet} that saves the result in an + * {@link MvcResult}. The {@code MvcResult} instance is expected to be available + * as the request attribute {@link MockMvc#MVC_RESULT_ATTRIBUTE}. + * + * @author Rossen Stoyanchev + * @author Rob Winch + */ +@SuppressWarnings("serial") +final class TestDispatcherServlet extends DispatcherServlet { + + /** + * Create a new instance with the given web application context. + */ + public TestDispatcherServlet(WebApplicationContext webApplicationContext) { + super(webApplicationContext); + } + + protected DefaultMvcResult getMvcResult(ServletRequest request) { + return (DefaultMvcResult) request.getAttribute(MockMvc.MVC_RESULT_ATTRIBUTE); + } + + @Override + protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { + HandlerExecutionChain chain = super.getHandler(request); + if (chain != null) { + DefaultMvcResult mvcResult = getMvcResult(request); + mvcResult.setHandler(chain.getHandler()); + mvcResult.setInterceptors(chain.getInterceptors()); + } + return chain; + } + + @Override + protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) + throws Exception { + + DefaultMvcResult mvcResult = getMvcResult(request); + mvcResult.setModelAndView(mv); + super.render(mv, request, response); + } + + @Override + protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, + Object handler, Exception ex) throws Exception { + + ModelAndView mav = super.processHandlerException(request, response, handler, ex); + + // We got this far, exception was processed.. + DefaultMvcResult mvcResult = getMvcResult(request); + mvcResult.setResolvedException(ex); + mvcResult.setModelAndView(mav); + + return mav; + } + +} diff --git a/src/main/java/org/springframework/test/web/server/matcher/HandlerMatchers.java b/src/main/java/org/springframework/test/web/server/matcher/HandlerMatchers.java deleted file mode 100644 index e8b8412..0000000 --- a/src/main/java/org/springframework/test/web/server/matcher/HandlerMatchers.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2002-2011 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.server.matcher; - -import static org.springframework.test.web.AssertionErrors.assertEquals; -import static org.springframework.test.web.AssertionErrors.assertTrue; - -import java.lang.reflect.Method; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.test.web.server.MvcResultMatcher; -import org.springframework.util.ReflectionUtils; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.ModelAndView; - -/** - * Matchers for applying assertions on the handler matched to a request. - * - */ -public abstract class HandlerMatchers { - - private HandlerMatchers() { - } - - public static MvcResultMatcher handlerMethod(final String methodName) { - return new HandlerMethodResultMatcher() { - protected void matchHandlerMethod(HandlerMethod handlerMethod) { - assertEquals("Method", methodName, handlerMethod.getMethod().getName()); - } - }; - } - - public static MvcResultMatcher handlerMethod(final Class> controllerType, - final String methodName, - final Class>...argumentTypes) { - return new HandlerMethodResultMatcher() { - protected void matchHandlerMethod(HandlerMethod handlerMethod) { - Method method = ReflectionUtils.findMethod(controllerType, methodName, argumentTypes); - assertTrue("Method not found", method != null); - assertEquals("Method", method, handlerMethod.getMethod()); - } - }; - } - - public static MvcResultMatcher handlerType(final Class> handlerType) { - return new HandlerResultMatcher() { - protected void matchHandler(Object handler) { - assertEquals("Handler type", handlerType, handler.getClass()); - } - }; - } - - private abstract static class HandlerResultMatcher implements MvcResultMatcher { - - public final void match(MockHttpServletRequest request, - MockHttpServletResponse response, - Object handler, - Exception handlerException, - ModelAndView mav) { - assertTrue("No matching handler", handler != null); - matchHandler(handler); - } - - protected abstract void matchHandler(Object handler); - } - - private abstract static class HandlerMethodResultMatcher extends HandlerResultMatcher { - - @Override - protected void matchHandler(Object handler) { - Class> type = handler.getClass(); - assertTrue("Expected HandlerMethod. Actual type " + type, HandlerMethod.class.isAssignableFrom(type)); - matchHandlerMethod((HandlerMethod) handler); - } - - protected abstract void matchHandlerMethod(HandlerMethod handlerMethod); - } - -} diff --git a/src/main/java/org/springframework/test/web/server/matcher/LoggingMatcher.java b/src/main/java/org/springframework/test/web/server/matcher/LoggingMatcher.java deleted file mode 100644 index fd43390..0000000 --- a/src/main/java/org/springframework/test/web/server/matcher/LoggingMatcher.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2002-2011 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.server.matcher; - -import java.io.UnsupportedEncodingException; -import java.util.Map; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.test.web.server.MvcResultMatcher; -import org.springframework.util.Assert; -import org.springframework.validation.BindingResult; -import org.springframework.validation.Errors; -import org.springframework.web.method.HandlerMethod; -import org.springframework.web.servlet.ModelAndView; - -public class LoggingMatcher implements MvcResultMatcher { - - private static final Log logger = LogFactory.getLog(LoggingMatcher.class); - - public void match(MockHttpServletRequest request, - MockHttpServletResponse response, - Object handler, - Exception handlerException, - ModelAndView mav) { - - StringBuilder sb = new StringBuilder(); - - appendRequest(sb, request); - appendHandler(sb, handler, handlerException); - appendModelAndView(sb, mav); - appendResponse(sb, response); - - logger.info(sb.toString()); - } - - private void appendRequest(StringBuilder sb, MockHttpServletRequest request) { - sb.append("\n\n" + request.getMethod() + " " + request.getRequestURI() + "\n"); - appendLabelAndValue(sb, "Params", request.getParameterMap()); - appendLabelAndValue(sb, "Headers", MockRequestMatchers.getHeaderValueMap(request)); - } - - private void appendHandler(StringBuilder sb, Object handler, Exception handlerException) { - if (handler == null) { - sb.append("\nSelected Handler: null\n"); - return; - } - - sb.append("\nSelected Handler:\n"); - if (!HandlerMethod.class.isInstance(handler)) { - appendLabelAndValue(sb, "Type", handler.getClass().getName()); - appendLabelAndValue(sb, "Method", "Not available"); - } - else { - HandlerMethod hm = (HandlerMethod) handler; - appendLabelAndValue(sb, "Type", hm.getBeanType().getName()); - appendLabel(sb, "Method"); - - sb.append(hm.getReturnType().getParameterType().getSimpleName()); - sb.append(" " + hm.getMethod().getName() + "("); - for (int i = 0; i < hm.getMethod().getParameterTypes().length; i++) { - if (i != 0) { - sb.append(", "); - } - sb.append(hm.getMethod().getParameterTypes()[i].getSimpleName()); - } - sb.append(") \n"); - } - - if (handlerException == null) { - sb.append("\nHandler Exception Raised: none\n"); - } - else { - sb.append("\nHandler Exception Raised:\n" + handlerException + "\n"); - } - } - - private void appendLabel(StringBuilder sb, String label) { - for (int i = 0; i < (17 - label.length()); i++) { - sb.append(" "); - } - sb.append(label + ": "); - } - - private void appendLabelAndValue(StringBuilder sb, String label, Object value) { - appendLabel(sb, label); - sb.append(value + "\n"); - } - - private void appendModelAndView(StringBuilder sb, ModelAndView mav) { - sb.append("\nModelAndView: "); - if (mav != null) { - sb.append("\n"); - appendView(sb, mav); - appendModel(sb, mav.getModel()); - } - else { - sb.append("null\n"); - } - } - - private void appendView(StringBuilder sb, ModelAndView mav) { - Assert.notNull(mav); - if (mav.isReference()) { - appendLabelAndValue(sb, "View name", "\"" + mav.getViewName() + "\""); - } - else { - appendLabelAndValue(sb, "View", mav.getView()); - } - } - - private void appendModel(StringBuilder sb, Mapmodel) { - if (model.size() == 0) { - appendLabelAndValue(sb, "Attributes", "none"); - sb.append("none"); - return; - } - for (String name : model.keySet()) { - if (!name.startsWith(BindingResult.MODEL_KEY_PREFIX)) { - Object value = model.get(name); - Errors errors = (Errors) model.get(BindingResult.MODEL_KEY_PREFIX + name); - if (errors == null) { - appendLabelAndValue(sb, "Attribute", name); - } - else { - appendLabelAndValue(sb, "Attribute", name + " has " + errors.getErrorCount() + " errors"); - } - if (logger.isTraceEnabled()) { - appendLabelAndValue(sb, "value", value); - if (errors != null) { - appendLabelAndValue(sb, "errors", errors.getAllErrors()); - } - } - } - } - } - - private void appendResponse(StringBuilder sb, MockHttpServletResponse response) { - sb.append("\nResponse:\n"); - appendLabelAndValue(sb, "status", response.getStatus()); - appendLabelAndValue(sb, "error message", response.getErrorMessage()); - appendLabelAndValue(sb, "headers", MockResponseMatchers.getHeaderValueMap(response)); - appendLabelAndValue(sb, "content type", response.getContentType()); - appendResponseBody(sb, response); - appendLabelAndValue(sb, "forwarded URL", response.getForwardedUrl()); - appendLabelAndValue(sb, "redirected URL", response.getRedirectedUrl()); - appendLabelAndValue(sb, "included URLs", response.getIncludedUrls()); - appendLabelAndValue(sb, "cookies", MockResponseMatchers.getCookieValueMap(response)); - sb.append("\n"); - } - - private void appendResponseBody(StringBuilder sb, MockHttpServletResponse response) { - String content; - try { - content = response.getContentAsString(); - - } catch (UnsupportedEncodingException e) { - String message = "Failed to get the response content: "; - content = message + e.toString(); - logger.error(message, e); - } - if (content != null) { - int length = content.length(); - if (length > 50) { - content = content.substring(0, 50); - appendLabelAndValue(sb, "response body", "[" + content + "] (50 of " + " " + length + " chars)"); - } - else { - appendLabelAndValue(sb, "response body", "[" + content + "]"); - } - } - } - -} diff --git a/src/main/java/org/springframework/test/web/server/matcher/MockRequestMatchers.java b/src/main/java/org/springframework/test/web/server/matcher/MockRequestMatchers.java deleted file mode 100644 index 27faf52..0000000 --- a/src/main/java/org/springframework/test/web/server/matcher/MockRequestMatchers.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2002-2011 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.server.matcher; - -import static org.springframework.test.web.AssertionErrors.assertEquals; - -import java.util.Enumeration; -import java.util.LinkedHashMap; -import java.util.Map; - -import javax.servlet.ServletRequest; -import javax.servlet.http.HttpSession; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.test.web.AssertionErrors; -import org.springframework.test.web.server.MvcResultMatcher; -import org.springframework.web.servlet.ModelAndView; - -/** - * Matchers assertions on a {@link MockHttpServletRequest}. - * - */ -public abstract class MockRequestMatchers { - - private MockRequestMatchers() { - } - - public static MvcResultMatcher requestAttributeValue(final String name, final Object value) { - return new MockRequestResultMatcher() { - protected void matchMockRequest(MockHttpServletRequest request) { - assertEquals("Request attribute", value, request.getAttribute(name)); - } - }; - } - - public static MvcResultMatcher requestAttributesPresent(final String...names) { - return new MockRequestResultMatcher() { - protected void matchMockRequest(MockHttpServletRequest request) { - AssertionErrors.assertNameValuesPresent("Request attribute", getRequestAttributeMap(request), names); - } - }; - } - - public static MvcResultMatcher requestAttributesNotPresent(final String...names) { - return new MockRequestResultMatcher() { - protected void matchMockRequest(MockHttpServletRequest request) { - AssertionErrors.assertNameValuesNotPresent("Request attribute", getRequestAttributeMap(request), names); - } - }; - } - - public static MvcResultMatcher sessionAttributeValue(final String name, final Object value) { - return new MockRequestResultMatcher() { - protected void matchMockRequest(MockHttpServletRequest request) { - assertEquals("Session attribute", value, request.getSession().getAttribute(name)); - } - }; - } - - public static MvcResultMatcher sessionAttributesPresent(final String...names) { - return new MockRequestResultMatcher() { - protected void matchMockRequest(MockHttpServletRequest request) { - HttpSession session = request.getSession(); - AssertionErrors.assertNameValuesPresent("Session attribute", getSessionAttributeMap(session), names); - } - }; - } - - public static MvcResultMatcher sessionAttributesNotPresent(final String...names) { - return new MockRequestResultMatcher() { - protected void matchMockRequest(MockHttpServletRequest request) { - HttpSession session = request.getSession(); - AssertionErrors.assertNameValuesNotPresent("Session attribute", getSessionAttributeMap(session), names); - } - }; - } - - static Map getHeaderValueMap(MockHttpServletRequest request) { - Map map = new LinkedHashMap (); - Enumeration> names = request.getHeaderNames(); - while (names.hasMoreElements()) { - String name = (String) names.nextElement(); - map.put(name, request.getHeader(name)); - } - return map; - } - - static Map getRequestAttributeMap(ServletRequest request) { - Map map = new LinkedHashMap (); - Enumeration> names = request.getAttributeNames(); - while (names.hasMoreElements()) { - String name = (String) names.nextElement(); - map.put(name, request.getAttribute(name)); - } - return map; - } - - static Map getSessionAttributeMap(HttpSession session) { - Map map = new LinkedHashMap (); - Enumeration> names = session.getAttributeNames(); - while (names.hasMoreElements()) { - String name = (String) names.nextElement(); - map.put(name, session.getAttribute(name)); - } - - return map; - } - - private abstract static class MockRequestResultMatcher implements MvcResultMatcher { - - public final void match(MockHttpServletRequest request, - MockHttpServletResponse response, - Object handler, - Exception handlerException, - ModelAndView mav) { - matchMockRequest(request); - } - - protected abstract void matchMockRequest(MockHttpServletRequest request); - } - -} diff --git a/src/main/java/org/springframework/test/web/server/matcher/MockResponseMatchers.java b/src/main/java/org/springframework/test/web/server/matcher/MockResponseMatchers.java deleted file mode 100644 index 2f41a30..0000000 --- a/src/main/java/org/springframework/test/web/server/matcher/MockResponseMatchers.java +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright 2002-2011 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.server.matcher; - -import static org.springframework.test.web.AssertionErrors.assertEquals; -import static org.springframework.test.web.AssertionErrors.assertTrue; -import static org.springframework.test.web.AssertionErrors.fail; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.LinkedHashMap; -import java.util.Map; - -import javax.servlet.http.Cookie; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.test.web.AssertionErrors; -import org.springframework.test.web.server.MvcResultMatcher; -import org.springframework.util.StringUtils; -import org.springframework.web.servlet.ModelAndView; - -/** - * Matchers for assertions on a {@link MockHttpServletResponse}. - * - */ -public abstract class MockResponseMatchers { - - private static final Log logger = LogFactory.getLog(MockResponseMatchers.class); - - private MockResponseMatchers() { - } - - public static MvcResultMatcher status(final int status) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) { - assertEquals("Status", status, response.getStatus()); - } - }; - } - - public static MvcResultMatcher errorMessage(final String errorMessage) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) { - assertEquals("Error message", errorMessage, response.getErrorMessage()); - } - }; - } - - public static MvcResultMatcher contentType(final String contentType) { - return new MvcResultMatcher() { - public void match(MockHttpServletRequest rq, MockHttpServletResponse response, Object h, Exception e, ModelAndView mav) { - if (StringUtils.hasText(response.getContentType())) { - assertEquals("Content type", contentType, response.getContentType()); - } - else { - String headerName = "Content-Type"; - assertEquals("Content-Type response header", contentType, response.getHeader(headerName)); - } - } - }; - } - - public static MvcResultMatcher responseBody(final String content) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) throws UnsupportedEncodingException { - assertEquals("Response body", content, response.getContentAsString()); - } - }; - } - - public static MvcResultMatcher responseBodyContains(final String text) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) throws UnsupportedEncodingException { - String body = response.getContentAsString(); - assertTrue("Response body <" + body + "> does not contain " + text, body.contains(text)); - } - }; - } - - public static MvcResultMatcher responseBodyAsByteArray(final byte[] content) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) { - assertEquals("Response body", content, response.getContentAsByteArray()); - } - }; - } - - public static MvcResultMatcher forwardedUrl(final String forwardUrl) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) { - assertEquals("Forwarded URL", forwardUrl, response.getForwardedUrl()); - } - }; - } - - public static MvcResultMatcher redirectedUrl(final String redirectUrl) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) { - assertEquals("Redirected URL", redirectUrl, response.getRedirectedUrl()); - } - }; - } - - public static MvcResultMatcher headersPresent(final String...headerNames) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) { - AssertionErrors.assertNameValuesPresent("Response header", getHeaderValueMap(response), headerNames); - } - }; - } - - public static MvcResultMatcher headersNotPresent(final String...headerNames) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) { - AssertionErrors.assertNameValuesNotPresent("Response header", getHeaderValueMap(response), headerNames); - } - }; - } - - public static MvcResultMatcher headerValue(final String headerName, final Object headerValue) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) { - assertEquals("Response header", headerValue, response.getHeader(headerName)); - } - }; - } - - public static MvcResultMatcher headerValueContains(final String headerName, final String text) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) { - AssertionErrors.assertNameValuesPresent("Response header", getHeaderValueMap(response), headerName); - Object value = response.getHeader(headerName); - assertEquals("Header value type", String.class, response.getHeader(headerName).getClass()); - assertTrue("Header '" + headerName + "' with value <" + value + "> does not contain <" + text + ">.", - ((String) value).contains(text)); - } - }; - } - - public static MvcResultMatcher cookiesPresent(final String...names) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) { - AssertionErrors.assertNameValuesPresent("Response cookie", getCookieValueMap(response), names); - } - }; - } - - public static MvcResultMatcher cookiesNotPresent(final String...names) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) { - AssertionErrors.assertNameValuesNotPresent("Response cookie", getCookieValueMap(response), names); - } - }; - } - - public static MvcResultMatcher cookieValue(final String name, final String value) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) { - assertEquals("Response cookie", value, response.getCookie(name).getValue()); - } - }; - } - - public static MvcResultMatcher cookieValueContains(final String cookieName, final String text) { - return new MockResponseResultMatcher() { - protected void matchMockResponse(MockHttpServletResponse response) { - AssertionErrors.assertNameValuesPresent("Response cookie", getCookieValueMap(response), cookieName); - String value = response.getCookie(cookieName).getValue(); - assertTrue("Cookie '" + cookieName + "' with value <" + value + "> does not contain <" + text + ">.", - value.contains(text)); - } - }; - } - - static Map getHeaderValueMap(MockHttpServletResponse response) { - Map headers = new LinkedHashMap (); - for (String name : response.getHeaderNames()) { - headers.put(name, response.getHeader(name)); - } - return headers; - } - - static Map getCookieValueMap(MockHttpServletResponse response) { - Map cookies = new LinkedHashMap (); - for (Cookie cookie : response.getCookies()) { - cookies.put(cookie.getName(), cookie.getValue()); - } - return cookies; - } - - private static abstract class MockResponseResultMatcher implements MvcResultMatcher { - - public void match(MockHttpServletRequest request, - MockHttpServletResponse response, - Object handler, - Exception handlerException, - ModelAndView mav) { - try { - matchMockResponse(response); - } catch (IOException e) { - logger.error(e.getMessage(), e); - fail("Failed mock response expectation: " + e.getMessage()); - } - } - - protected abstract void matchMockResponse(MockHttpServletResponse response) throws IOException; - } - -} diff --git a/src/main/java/org/springframework/test/web/server/matcher/ModelAndViewMatchers.java b/src/main/java/org/springframework/test/web/server/matcher/ModelAndViewMatchers.java deleted file mode 100644 index 51a0e84..0000000 --- a/src/main/java/org/springframework/test/web/server/matcher/ModelAndViewMatchers.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2002-2011 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.server.matcher; - -import static org.springframework.test.web.AssertionErrors.assertEquals; -import static org.springframework.test.web.AssertionErrors.assertTrue; -import static org.springframework.test.web.AssertionErrors.fail; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.test.web.AssertionErrors; -import org.springframework.test.web.server.MvcResultMatcher; -import org.springframework.validation.BindingResult; -import org.springframework.web.servlet.ModelAndView; - -/** - * Matchers for assertions on a {@link ModelAndView}. - * - */ -public abstract class ModelAndViewMatchers { - - private ModelAndViewMatchers() { - } - - public static MvcResultMatcher modelAttribute(final String name, final Object value) { - return new ModelAndViewResultMatcher() { - protected void matchModelAndView(ModelAndView mav) { - assertEquals("Model attribute", value, mav.getModel().get(name)); - } - }; - } - - public static MvcResultMatcher modelAttributesPresent(final String...names) { - return new ModelAndViewResultMatcher() { - protected void matchModelAndView(ModelAndView mav) { - AssertionErrors.assertNameValuesPresent("Model attribute", mav.getModelMap(), names); - } - }; - } - - public static MvcResultMatcher modelAttributesNotPresent(final String...names) { - return new ModelAndViewResultMatcher() { - protected void matchModelAndView(ModelAndView mav) { - AssertionErrors.assertNameValuesNotPresent("Model attribute", mav.getModelMap(), names); - } - }; - } - - public static MvcResultMatcher noBindingErrors() { - return new ModelAndViewResultMatcher() { - protected void matchModelAndView(ModelAndView mav) { - for (String name : mav.getModel().keySet()) { - if (!name.startsWith(BindingResult.MODEL_KEY_PREFIX)) { - continue; - } - BindingResult result = (BindingResult) mav.getModel().get(name); - if (result.hasErrors()) { - fail("Model attribute <" + name + "> has binding errors: " + result); - } - } - } - }; - } - - public static MvcResultMatcher modelAttributesWithNoErrors(final String...names) { - return new ModelAndViewResultMatcher() { - protected void matchModelAndView(ModelAndView mav) { - AssertionErrors.assertNameValuesPresent("Model attribute", mav.getModelMap(), names); - for (String name : names) { - BindingResult result = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + name); - if (result.hasErrors()) { - fail("Expected no bind errors for model attribute <" + name + "> but got " + result); - } - } - } - }; - } - - public static MvcResultMatcher modelAttributesWithErrors(final String...names) { - return new ModelAndViewResultMatcher() { - protected void matchModelAndView(ModelAndView mav) { - AssertionErrors.assertNameValuesPresent("Model attribute", mav.getModelMap(), names); - for (String name : names) { - BindingResult result = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + name); - assertTrue("Expected bind errors for model attribute <" + name + ">", result.hasErrors()); - } - } - }; - } - - public static MvcResultMatcher viewName(final String viewName) { - return new MvcResultMatcher() { - public void match(MockHttpServletRequest request, - MockHttpServletResponse response, - Object handler, - Exception handlerException, - ModelAndView mav) { - assertEquals("View name", viewName, mav.getViewName()); - } - }; - } - - private abstract static class ModelAndViewResultMatcher implements MvcResultMatcher { - - public final void match(MockHttpServletRequest request, - MockHttpServletResponse response, - Object handler, - Exception handlerException, - ModelAndView mav) { - assertTrue("No ModelAndView", mav != null); - matchModelAndView(mav); - } - - protected abstract void matchModelAndView(ModelAndView mav); - } - -} diff --git a/src/main/java/org/springframework/test/web/server/matcher/MvcResultMatchers.java b/src/main/java/org/springframework/test/web/server/matcher/MvcResultMatchers.java deleted file mode 100644 index ed9636d..0000000 --- a/src/main/java/org/springframework/test/web/server/matcher/MvcResultMatchers.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2002-2011 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.server.matcher; - -import org.springframework.test.web.server.MvcResultMatcher; - -/** - * The {@link MvcResultMatchers}s in this class overlap with other {@code *Matchers} in this package. - * The intent is to compile a list {@link MvcResultMatcher}s recommended for common use. - * - */ -public class MvcResultMatchers { - - public static MvcResultMatcher status(int status) { - return MockResponseMatchers.status(status); - } - - public static MvcResultMatcher contentType(String contentType) { - return MockResponseMatchers.contentType(contentType); - } - - public static MvcResultMatcher responseBody(String content) { - return MockResponseMatchers.responseBody(content); - } - - public static MvcResultMatcher responseBodyContains(String text) { - return MockResponseMatchers.responseBodyContains(text); - } - - public static MvcResultMatcher forwardedUrl(String forwardUrl) { - return MockResponseMatchers.forwardedUrl(forwardUrl); - } - - public static MvcResultMatcher redirectedUrl(String redirectUrl) { - return MockResponseMatchers.redirectedUrl(redirectUrl); - } - - public static MvcResultMatcher viewName(String viewName) { - return ModelAndViewMatchers.viewName(viewName); - } - - public static MvcResultMatcher noBindingErrors() { - return ModelAndViewMatchers.noBindingErrors(); - } - - public static MvcResultMatcher modelAttributesWithErrors(String...names) { - return ModelAndViewMatchers.modelAttributesWithErrors(names); - } - - public static MvcResultMatcher modelAttributesPresent(String...names) { - return ModelAndViewMatchers.modelAttributesPresent(names); - } - - public static MvcResultMatcher loggingMatcher() { - return new LoggingMatcher(); - } - -} diff --git a/src/main/java/org/springframework/test/web/server/MvcResultMatcher.java b/src/main/java/org/springframework/test/web/server/package-info.java similarity index 54% rename from src/main/java/org/springframework/test/web/server/MvcResultMatcher.java rename to src/main/java/org/springframework/test/web/server/package-info.java index b42bcbf..dcb42c0 100644 --- a/src/main/java/org/springframework/test/web/server/MvcResultMatcher.java +++ b/src/main/java/org/springframework/test/web/server/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2011-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. @@ -14,22 +14,8 @@ * limitations under the License. */ -package org.springframework.test.web.server; - -import org.springframework.mock.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; -import org.springframework.web.servlet.ModelAndView; - /** - * Defines a match operation on the results of a processed request. - * + * Contains server-side support for testing Spring MVC applications. + * @see org.springframework.test.web.server.MockMvc */ -public interface MvcResultMatcher { - - void match(MockHttpServletRequest request, - MockHttpServletResponse response, - Object handler, - Exception handlerException, - ModelAndView mav); - -} +package org.springframework.test.web.server; diff --git a/src/main/java/org/springframework/test/web/server/request/MockHttpServletRequestBuilder.java b/src/main/java/org/springframework/test/web/server/request/MockHttpServletRequestBuilder.java new file mode 100644 index 0000000..9a005ae --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/request/MockHttpServletRequestBuilder.java @@ -0,0 +1,686 @@ +/* + * Copyright 2011-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.server.request; + +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; + +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; +import javax.servlet.http.Cookie; + +import org.springframework.beans.Mergeable; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.test.web.server.MockMvc; +import org.springframework.test.web.server.RequestBuilder; +import org.springframework.test.web.server.setup.MockMvcBuilders; +import org.springframework.util.Assert; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.ObjectUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.ValueConstants; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.FlashMap; +import org.springframework.web.servlet.FlashMapManager; +import org.springframework.web.servlet.support.SessionFlashMapManager; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.util.UriUtils; + +/** + * Default builder for {@link MockHttpServletRequest} required as input to + * perform request in {@link MockMvc}. + * + * Application tests will typically access this builder through the static + * factory methods in {@link MockMvcBuilders}. + * + * @author Rossen Stoyanchev + * @author Arjen Poutsma + */ +public class MockHttpServletRequestBuilder implements RequestBuilder, Mergeable { + + private final UriComponents uriComponents; + + private final HttpMethod method; + + private final MultiValueMap
headers = new LinkedMultiValueMap (); + + private String contentType; + + private byte[] content; + + private final MultiValueMap parameters = new LinkedMultiValueMap (); + + private final List cookies = new ArrayList (); + + private Locale locale; + + private String characterEncoding; + + private Principal principal; + + private Boolean secure; + + private final Map attributes = new LinkedHashMap (); + + private MockHttpSession session; + + private final Map sessionAttributes = new LinkedHashMap (); + + private final Map flashAttributes = new LinkedHashMap (); + + private String contextPath = ""; + + private String servletPath = ""; + + private String pathInfo = ValueConstants.DEFAULT_NONE; + + private final List postProcessors = + new ArrayList (); + + + /** + * Package private constructor. To get an instance, use static factory + * methods in {@link MockMvcRequestBuilders}. + * + * Although this class cannot be extended, additional ways to initialize + * the {@code MockHttpServletRequest} can be plugged in via + * {@link #with(RequestPostProcessor)}. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + */ + MockHttpServletRequestBuilder(HttpMethod httpMethod, String urlTemplate, Object... urlVariables) { + + Assert.notNull(urlTemplate, "uriTemplate is required"); + Assert.notNull(httpMethod, "httpMethod is required"); + + this.uriComponents = UriComponentsBuilder.fromUriString(urlTemplate).buildAndExpand(urlVariables).encode(); + this.method = httpMethod; + } + + /** + * Add a request parameter to the {@link MockHttpServletRequest}. + * If called more than once, the new values are added. + * + * @param name the parameter name + * @param values one or more values + */ + public MockHttpServletRequestBuilder param(String name, String... values) { + addToMultiValueMap(this.parameters, name, values); + return this; + } + + /** + * Add a header to the request. Values are always added. + * + * @param name the header name + * @param values one or more header values + */ + public MockHttpServletRequestBuilder header(String name, Object... values) { + addToMultiValueMap(this.headers, name, values); + return this; + } + + /** + * Add all headers to the request. Values are always added. + * + * @param httpHeaders the headers and values to add + */ + public MockHttpServletRequestBuilder headers(HttpHeaders httpHeaders) { + for (String name : httpHeaders.keySet()) { + Object[] values = ObjectUtils.toObjectArray(httpHeaders.get(name).toArray()); + addToMultiValueMap(this.headers, name, values); + } + return this; + } + + /** + * Set the 'Content-Type' header of the request. + * + * @param mediaType the content type + */ + public MockHttpServletRequestBuilder contentType(MediaType mediaType) { + Assert.notNull(mediaType, "'contentType' must not be null"); + this.contentType = mediaType.toString(); + this.headers.set("Content-Type", this.contentType); + return this; + } + + /** + * Set the 'Accept' header to the given media type(s). + * + * @param mediaTypes one or more media types + */ + public MockHttpServletRequestBuilder accept(MediaType... mediaTypes) { + Assert.notEmpty(mediaTypes, "No 'Accept' media types"); + this.headers.set("Accept", MediaType.toString(Arrays.asList(mediaTypes))); + return this; + } + + /** + * Set the request body. + * + * @param content the body content + */ + public MockHttpServletRequestBuilder body(byte[] content) { + this.content = content; + return this; + } + + /** + * Add the given cookies to the request. Cookies are always added. + * + * @param cookies the cookies to add + */ + public MockHttpServletRequestBuilder cookie(Cookie... cookies) { + Assert.notNull(cookies, "'cookies' must not be null"); + Assert.notEmpty(cookies, "'cookies' must not be empty"); + this.cookies.addAll(Arrays.asList(cookies)); + return this; + } + + /** + * Set the locale of the request. + * + * @param locale the locale + */ + public MockHttpServletRequestBuilder locale(Locale locale) { + this.locale = locale; + return this; + } + + /** + * Set the character encoding of the request. + * + * @param encoding the character encoding + */ + public MockHttpServletRequestBuilder characterEncoding(String encoding) { + this.characterEncoding = encoding; + return this; + } + + /** + * Set a request attribute. + * + * @param name the attribute name + * @param value the attribute value + */ + public MockHttpServletRequestBuilder requestAttr(String name, Object value) { + addAttributeToMap(this.attributes, name, value); + return this; + } + + /** + * Set a session attribute. + * + * @param name the session attribute name + * @param value the session attribute value + */ + public MockHttpServletRequestBuilder sessionAttr(String name, Object value) { + addAttributeToMap(this.sessionAttributes, name, value); + return this; + } + + /** + * Set session attributes. + * + * @param sessionAttributes the session attributes + */ + public MockHttpServletRequestBuilder sessionAttrs(Map
sessionAttributes) { + Assert.notEmpty(sessionAttributes, "'sessionAttrs' must not be empty"); + for (String name : sessionAttributes.keySet()) { + sessionAttr(name, sessionAttributes.get(name)); + } + return this; + } + + /** + * Set an "input" flash attribute. + * + * @param name the flash attribute name + * @param value the flash attribute value + */ + public MockHttpServletRequestBuilder flashAttr(String name, Object value) { + addAttributeToMap(this.flashAttributes, name, value); + return this; + } + + /** + * Set flash attributes. + * + * @param flashAttributes the flash attributes + */ + public MockHttpServletRequestBuilder flashAttrs(Map flashAttributes) { + Assert.notEmpty(flashAttributes, "'flashAttrs' must not be empty"); + for (String name : flashAttributes.keySet()) { + flashAttr(name, flashAttributes.get(name)); + } + return this; + } + + /** + * Set the HTTP session to use, possibly re-used across requests. + * + * Individual attributes provided via {@link #sessionAttr(String, Object)} + * override the content of the session provided here. + * + * @param session the HTTP session + */ + public MockHttpServletRequestBuilder session(MockHttpSession session) { + Assert.notNull(session, "'session' must not be null"); + this.session = session; + return this; + } + + /** + * Set the principal of the request. + * + * @param principal the principal + */ + public MockHttpServletRequestBuilder principal(Principal principal) { + Assert.notNull(principal, "'principal' must not be null"); + this.principal = principal; + return this; + } + + /** + * Specify the portion of the requestURI that represents the context path. + * The context path, if specified, must match to the start of the request + * URI. + * + *
In most cases, tests can be written by omitting the context path from + * the requestURI. This is because most applications don't actually depend + * on the name under which they're deployed. If specified here, the context + * path must start with a "/" and must not end with a "/". + * + * @see HttpServletRequest.getContextPath() + */ + public MockHttpServletRequestBuilder contextPath(String contextPath) { + if (StringUtils.hasText(contextPath)) { + Assert.isTrue(contextPath.startsWith("/"), "Context path must start with a '/'"); + Assert.isTrue(!contextPath.endsWith("/"), "Context path must not end with a '/'"); + } + this.contextPath = (contextPath != null) ? contextPath : ""; + return this; + } + + /** + * Specify the portion of the requestURI that represents the path to which + * the Servlet is mapped. This is typically a portion of the requestURI + * after the context path. + * + *
In most cases, tests can be written by omitting the servlet path from + * the requestURI. This is because most applications don't actually depend + * on the prefix to which a servlet is mapped. For example if a Servlet is + * mapped to {@code "/main/*"}, tests can be written with the requestURI + * {@code "/accounts/1"} as opposed to {@code "/main/accounts/1"}. + * If specified here, the servletPath must start with a "/" and must not + * end with a "/". + * + * @see HttpServletRequest.getServletPath() + */ + public MockHttpServletRequestBuilder servletPath(String servletPath) { + if (StringUtils.hasText(servletPath)) { + Assert.isTrue(servletPath.startsWith("/"), "Servlet path must start with a '/'"); + Assert.isTrue(!servletPath.endsWith("/"), "Servlet path must not end with a '/'"); + } + this.servletPath = (servletPath != null) ? servletPath : ""; + return this; + } + + /** + * Specify the portion of the requestURI that represents the pathInfo. + * + *
If left unspecified (recommended), the pathInfo will be automatically + * derived by removing the contextPath and the servletPath from the + * requestURI and using any remaining part. If specified here, the pathInfo + * must start with a "/". + * + *
If specified, the pathInfo will be used as is. + * + * @see HttpServletRequest.getServletPath() + */ + public MockHttpServletRequestBuilder pathInfo(String pathInfo) { + if (StringUtils.hasText(pathInfo)) { + Assert.isTrue(pathInfo.startsWith("/"), "pathInfo must start with a '/'"); + } + this.pathInfo = pathInfo; + return this; + } + + /** + * Set the secure property of the {@link ServletRequest} indicating use of a + * secure channel, such as HTTPS. + * + * @param secure whether the request is using a secure channel + */ + public MockHttpServletRequestBuilder secure(boolean secure){ + this.secure = secure; + return this; + } + + /** + * An extension point for further initialization of {@link MockHttpServletRequest} + * in ways not built directly into the {@code MockHttpServletRequestBuilder}. + * Implementation of this interface can have builder-style methods themselves + * and be made accessible through static factory methods. + * + * @param postProcessor a post-processor to add + */ + public MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor) { + Assert.notNull(postProcessor, "postProcessor is required"); + this.postProcessors.add(postProcessor); + return this; + } + + /** + * {@inheritDoc} + * @return always returns {@code true}. + */ + public boolean isMergeEnabled() { + return true; + } + + /** + * Merges the properties of the "parent" RequestBuilder accepting values + * only if not already set in "this" instance. + * + * @param parent the parent {@code RequestBuilder} to inherit properties from + * @return the result of the merge + */ + public Object merge(Object parent) { + if (parent == null) { + return this; + } + if (!(parent instanceof MockHttpServletRequestBuilder)) { + throw new IllegalArgumentException("Cannot merge with [" + parent.getClass().getName() + "]"); + } + + MockHttpServletRequestBuilder parentBuilder = (MockHttpServletRequestBuilder) parent; + + for (String headerName : parentBuilder.headers.keySet()) { + if (!this.headers.containsKey(headerName)) { + this.headers.put(headerName, parentBuilder.headers.get(headerName)); + } + } + + if (this.contentType == null) { + this.contentType = parentBuilder.contentType; + } + + if (this.content == null) { + this.content = parentBuilder.content; + } + + for (String paramName : parentBuilder.parameters.keySet()) { + if (!this.parameters.containsKey(paramName)) { + this.parameters.put(paramName, parentBuilder.parameters.get(paramName)); + } + } + + for (Cookie cookie : parentBuilder.cookies) { + if (!containsCookie(cookie)) { + this.cookies.add(cookie); + } + } + + if (this.locale == null) { + this.locale = parentBuilder.locale; + } + + if (this.characterEncoding == null) { + this.characterEncoding = parentBuilder.characterEncoding; + } + + if (this.principal == null) { + this.principal = parentBuilder.principal; + } + + if (this.secure == null) { + this.secure = parentBuilder.secure; + } + + for (String attributeName : parentBuilder.attributes.keySet()) { + if (!this.attributes.containsKey(attributeName)) { + this.attributes.put(attributeName, parentBuilder.attributes.get(attributeName)); + } + } + + if (this.session == null) { + this.session = parentBuilder.session; + } + + for (String sessionAttributeName : parentBuilder.sessionAttributes.keySet()) { + if (!this.sessionAttributes.containsKey(sessionAttributeName)) { + this.sessionAttributes.put(sessionAttributeName, parentBuilder.sessionAttributes.get(sessionAttributeName)); + } + } + + for (String flashAttributeName : parentBuilder.flashAttributes.keySet()) { + if (!this.flashAttributes.containsKey(flashAttributeName)) { + this.flashAttributes.put(flashAttributeName, parentBuilder.flashAttributes.get(flashAttributeName)); + } + } + + if (!StringUtils.hasText(this.contextPath)) { + this.contextPath = parentBuilder.contextPath; + } + + if (!StringUtils.hasText(this.servletPath)) { + this.servletPath = parentBuilder.servletPath; + } + + if (ValueConstants.DEFAULT_NONE.equals(this.pathInfo)) { + this.pathInfo = parentBuilder.pathInfo; + } + + this.postProcessors.addAll(parentBuilder.postProcessors); + + return this; + } + + private boolean containsCookie(Cookie cookie) { + for (Cookie c : this.cookies) { + if (ObjectUtils.nullSafeEquals(c.getName(), cookie.getName())) { + return true; + } + } + return false; + } + + /** + * Build a {@link MockHttpServletRequest}. + */ + public final MockHttpServletRequest buildRequest(ServletContext servletContext) { + + MockHttpServletRequest request = createServletRequest(servletContext); + + String requestUri = this.uriComponents.getPath(); + request.setRequestURI(requestUri); + + updatePathRequestProperties(request, requestUri); + + if (this.uriComponents.getScheme() != null) { + request.setScheme(this.uriComponents.getScheme()); + } + if (this.uriComponents.getHost() != null) { + request.setServerName(uriComponents.getHost()); + } + if (this.uriComponents.getPort() != -1) { + request.setServerPort(this.uriComponents.getPort()); + } + + request.setMethod(this.method.name()); + + for (String name : this.headers.keySet()) { + for (Object value : this.headers.get(name)) { + request.addHeader(name, value); + } + } + + try { + if (this.uriComponents.getQuery() != null) { + String query = UriUtils.decode(this.uriComponents.getQuery(), "UTF-8"); + request.setQueryString(query); + } + + for (Entry
> entry : this.uriComponents.getQueryParams().entrySet()) { + for (String value : entry.getValue()) { + request.addParameter( + UriUtils.decode(entry.getKey(), "UTF-8"), + UriUtils.decode(value, "UTF-8")); + } + } + } + catch (UnsupportedEncodingException ex) { + // shouldn't happen + } + + for (String name : this.parameters.keySet()) { + for (String value : this.parameters.get(name)) { + request.addParameter(name, value); + } + } + + request.setContentType(this.contentType); + request.setContent(this.content); + + request.setCookies(this.cookies.toArray(new Cookie[this.cookies.size()])); + + if (this.locale != null) { + request.addPreferredLocale(this.locale); + } + + request.setCharacterEncoding(this.characterEncoding); + + request.setUserPrincipal(this.principal); + + if (this.secure != null) { + request.setSecure(this.secure); + } + + for (String name : this.attributes.keySet()) { + request.setAttribute(name, this.attributes.get(name)); + } + + // Set session before session and flash attributes + + if (this.session != null) { + request.setSession(this.session); + } + + for (String name : this.sessionAttributes.keySet()) { + request.getSession().setAttribute(name, this.sessionAttributes.get(name)); + } + + FlashMap flashMap = new FlashMap(); + flashMap.putAll(this.flashAttributes); + + FlashMapManager flashMapManager = getFlashMapManager(request); + flashMapManager.saveOutputFlashMap(flashMap, request, new MockHttpServletResponse()); + + // Apply post-processors at the very end + + for (RequestPostProcessor postProcessor : this.postProcessors) { + request = postProcessor.postProcessRequest(request); + Assert.notNull(request, "Post-processor [" + postProcessor.getClass().getName() + "] returned null"); + } + + return request; + } + + /** + * Creates a new {@link MockHttpServletRequest} based on the given + * {@link ServletContext}. Can be overridden in sub-classes. + */ + protected MockHttpServletRequest createServletRequest(ServletContext servletContext) { + return new MockHttpServletRequest(servletContext); + } + + /** + * Update the contextPath, servletPath, and pathInfo of the request. + */ + private void updatePathRequestProperties(MockHttpServletRequest request, String requestUri) { + + Assert.isTrue(requestUri.startsWith(this.contextPath), + "requestURI [" + requestUri + "] does not start with contextPath [" + this.contextPath + "]"); + + request.setContextPath(this.contextPath); + request.setServletPath(this.servletPath); + + if (ValueConstants.DEFAULT_NONE.equals(this.pathInfo)) { + + Assert.isTrue(requestUri.startsWith(this.contextPath + this.servletPath), + "Invalid servletPath [" + this.servletPath + "] for requestURI [" + requestUri + "]"); + + String extraPath = requestUri.substring(this.contextPath.length() + this.servletPath.length()); + this.pathInfo = (StringUtils.hasText(extraPath)) ? extraPath : null; + } + + request.setPathInfo(this.pathInfo); + } + + private FlashMapManager getFlashMapManager(MockHttpServletRequest request) { + FlashMapManager flashMapManager = null; + try { + ServletContext servletContext = request.getServletContext(); + WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); + flashMapManager = wac.getBean(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class); + } + catch (IllegalStateException ex) { + } + catch (NoSuchBeanDefinitionException ex) { + } + return (flashMapManager != null) ? flashMapManager : new SessionFlashMapManager(); + } + + private static void addToMultiValueMap(MultiValueMap map, String name, T[] values) { + Assert.hasLength(name, "'name' must not be empty"); + Assert.notNull(values, "'values' is required"); + Assert.notEmpty(values, "'values' must not be empty"); + for (T value : values) { + map.add(name, value); + } + } + + private static void addAttributeToMap(Map map, String name, Object value) { + Assert.hasLength(name, "'name' must not be empty"); + Assert.notNull(value, "'value' must not be null"); + map.put(name, value); + } + +} diff --git a/src/main/java/org/springframework/test/web/server/request/MockMultipartHttpServletRequestBuilder.java b/src/main/java/org/springframework/test/web/server/request/MockMultipartHttpServletRequestBuilder.java new file mode 100644 index 0000000..6353a76 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/request/MockMultipartHttpServletRequestBuilder.java @@ -0,0 +1,105 @@ +/* + * Copyright 2011 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.server.request; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletContext; + +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.mock.web.MockMultipartHttpServletRequest; + +/** + * Default builder for {@link MockMultipartHttpServletRequest}. + * + * @author Rossen Stoyanchev + * @author Arjen Poutsma + */ +public class MockMultipartHttpServletRequestBuilder extends MockHttpServletRequestBuilder { + + private final List files = new ArrayList (); + + + /** + * Package private constructor. Use static factory methods in + * {@link MockMvcRequestBuilders}. + * + * For other ways to initialize a {@code MockMultipartHttpServletRequest}, + * see {@link #with(RequestPostProcessor)} and the + * {@link RequestPostProcessor} extension point. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + */ + MockMultipartHttpServletRequestBuilder(String urlTemplate, Object... urlVariables) { + super(HttpMethod.POST, urlTemplate, urlVariables); + super.contentType(MediaType.MULTIPART_FORM_DATA); + } + + /** + * Create a new MockMultipartFile with the given content. + * + * @param name the name of the file + * @param content the content of the file + */ + public MockMultipartHttpServletRequestBuilder file(String name, byte[] content) { + this.files.add(new MockMultipartFile(name, content)); + return this; + } + + /** + * Add the given MockMultipartFile. + * + * @param file the multipart file + */ + public MockMultipartHttpServletRequestBuilder file(MockMultipartFile file) { + this.files.add(file); + return this; + } + + @Override + public Object merge(Object parent) { + if (parent == null) { + return this; + } + if (!(parent instanceof MockMultipartHttpServletRequestBuilder)) { + throw new IllegalArgumentException("Cannot merge with [" + parent.getClass().getName() + "]"); + } + + super.merge(parent); + + MockMultipartHttpServletRequestBuilder parentBuilder = (MockMultipartHttpServletRequestBuilder) parent; + this.files.addAll(parentBuilder.files); + + return this; + } + + @Override + protected final MockHttpServletRequest createServletRequest(ServletContext servletContext) { + MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest(); + for (MockMultipartFile file : this.files) { + request.addFile(file); + } + return request; + } + +} diff --git a/src/main/java/org/springframework/test/web/server/request/MockMvcRequestBuilders.java b/src/main/java/org/springframework/test/web/server/request/MockMvcRequestBuilders.java new file mode 100644 index 0000000..58719bb --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/request/MockMvcRequestBuilders.java @@ -0,0 +1,97 @@ +/* + * Copyright 2002-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.server.request; + +import org.springframework.http.HttpMethod; +import org.springframework.test.web.server.RequestBuilder; + +/** + * Static factory methods for {@link RequestBuilder}s. + * + *
Eclipse users: consider adding this class as a Java + * editor favorite. To navigate, open the Preferences and type "favorites". + * + * @author Arjen Poutsma + * @author Rossen Stoyanchev + * @since 3.2 + */ +public abstract class MockMvcRequestBuilders { + + private MockMvcRequestBuilders() { + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a GET request. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + */ + public static MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables) { + return new MockHttpServletRequestBuilder(HttpMethod.GET, urlTemplate, urlVariables); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a POST request. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + */ + public static MockHttpServletRequestBuilder post(String urlTemplate, Object... urlVariables) { + return new MockHttpServletRequestBuilder(HttpMethod.POST, urlTemplate, urlVariables); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a PUT request. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + */ + public static MockHttpServletRequestBuilder put(String urlTemplate, Object... urlVariables) { + return new MockHttpServletRequestBuilder(HttpMethod.PUT, urlTemplate, urlVariables); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a DELETE request. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + */ + public static MockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVariables) { + return new MockHttpServletRequestBuilder(HttpMethod.DELETE, urlTemplate, urlVariables); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a request with the given HTTP method. + * + * @param httpMethod the HTTP method + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + */ + public static MockHttpServletRequestBuilder request(HttpMethod httpMethod, String urlTemplate, Object... urlVars) { + return new MockHttpServletRequestBuilder(httpMethod, urlTemplate, urlVars); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a multipart request. + * + * @param urlTemplate a URL template; the resulting URL will be encoded + * @param urlVariables zero or more URL variables + */ + public static MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, Object... urlVariables) { + return new MockMultipartHttpServletRequestBuilder(urlTemplate, urlVariables); + } + +} diff --git a/src/main/java/org/springframework/test/web/server/request/RequestPostProcessor.java b/src/main/java/org/springframework/test/web/server/request/RequestPostProcessor.java new file mode 100644 index 0000000..d8bfd26 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/request/RequestPostProcessor.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-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.server.request; + +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * Extension point for applications or 3rd party libraries that wish to further + * initialize a {@link MockHttpServletRequest} instance after it has been built + * by {@link MockHttpServletRequestBuilder} or its sub-class + * {@link MockMultipartHttpServletRequestBuilder}. + * + *
Implementations of this interface can be provided to + * {@link MockHttpServletRequestBuilder#with(RequestPostProcessor)} at the time + * when a request is about to be performed. + * + * @author Rossen Stoyanchev + * @author Rob Winch + */ +public interface RequestPostProcessor { + + /** + * Post-process the given {@code MockHttpServletRequest} after its creation + * and initialization through a {@code MockHttpServletRequestBuilder}. + * + * @param request the request to initialize + * @return the request to use, either the one passed in or a wrapped one; + */ + MockHttpServletRequest postProcessRequest(MockHttpServletRequest request); + +} diff --git a/src/main/java/org/springframework/test/web/server/request/package-info.java b/src/main/java/org/springframework/test/web/server/request/package-info.java new file mode 100644 index 0000000..27377a4 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/request/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2011-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. + */ + +/** + * Contains built-in {@link org.springframework.test.web.server.RequestBuilder} + * implementations. Use + * {@link org.springframework.test.web.server.request.MockMvcRequestBuilders} + * to gain access to instances of those implementations. + */ +package org.springframework.test.web.server.request; diff --git a/src/main/java/org/springframework/test/web/server/result/ContentResultMatchers.java b/src/main/java/org/springframework/test/web/server/result/ContentResultMatchers.java new file mode 100644 index 0000000..7206608 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/ContentResultMatchers.java @@ -0,0 +1,177 @@ +/* + * Copyright 2011-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.server.result; + +import static org.springframework.test.web.AssertionErrors.assertEquals; +import static org.springframework.test.web.AssertionErrors.assertTrue; + +import java.util.Map; + +import javax.servlet.http.HttpServletResponse; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.http.MediaType; +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultMatcher; +import org.springframework.test.web.support.XmlExpectationsHelper; +import org.w3c.dom.Node; + +/** + * Factory for response content assertions. An instance of this class is + * typically accessed via {@link MockMvcResultMatchers#content()}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class ContentResultMatchers { + + private final XmlExpectationsHelper xmlHelper; + + + /** + * Protected constructor. + * Use {@link MockMvcResultMatchers#content()}. + */ + protected ContentResultMatchers() { + this.xmlHelper = new XmlExpectationsHelper(); + } + + /** + * Assert the ServletResponse content type. + */ + public ResultMatcher mimeType(String contentType) { + return mimeType(MediaType.parseMediaType(contentType)); + } + + /** + * Assert the ServletResponse content type after parsing it as a MediaType. + */ + public ResultMatcher mimeType(final MediaType contentType) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + String actual = result.getResponse().getContentType(); + assertTrue("Content type not set", actual != null); + assertEquals("Content type", contentType, MediaType.parseMediaType(actual)); + } + }; + } + + /** + * Assert the character encoding in the ServletResponse. + * @see HttpServletResponse#getCharacterEncoding() + */ + public ResultMatcher encoding(final String characterEncoding) { + return new ResultMatcher() { + public void match(MvcResult result) { + String actual = result.getResponse().getCharacterEncoding(); + assertEquals("Character encoding", characterEncoding, actual); + } + }; + } + + /** + * Assert the response body content with a Hamcrest {@link Matcher}. + *
+ * mockMvc.perform(get("/path")) + * .andExpect(content(containsString("text"))); + *+ */ + public ResultMatcher string(final Matcher super String> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + MatcherAssert.assertThat("Response content", result.getResponse().getContentAsString(), matcher); + } + }; + } + + /** + * Assert the response body content as a String. + */ + public ResultMatcher string(String content) { + return string(Matchers.equalTo(content)); + } + + /** + * Assert the response body content as a byte array. + */ + public ResultMatcher bytes(final byte[] expectedContent) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + byte[] content = result.getResponse().getContentAsByteArray(); + MatcherAssert.assertThat("Response content", content, Matchers.equalTo(expectedContent)); + } + }; + } + + /** + * Parse the response content and the given string as XML and assert the two + * are "similar" - i.e. they contain the same elements and attributes + * regardless of order. + * + *Use of this matcher requires the XMLUnit library. + * + * @param xmlContent the expected XML content + * @see MockMvcResultMatchers#xpath(String, Object...) + * @see MockMvcResultMatchers#xpath(String, Map, Object...) + */ + public ResultMatcher xml(final String xmlContent) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + String content = result.getResponse().getContentAsString(); + xmlHelper.assertXmlEqual(xmlContent, content); + } + }; + } + + // TODO: XML validation + + /** + * Parse the response content as {@link Node} and apply the given Hamcrest + * {@link Matcher}. + * + * @see org.hamcrest.Matchers#hasXPath + */ + public ResultMatcher node(final Matcher super Node> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + String content = result.getResponse().getContentAsString(); + xmlHelper.assertNode(content, matcher); + } + }; + } + + /** + * Parse the response content as {@link DOMSource} and apply the given + * Hamcrest {@link Matcher}. + * + * @see xml-matchers + */ + public ResultMatcher source(final Matcher super Source> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + String content = result.getResponse().getContentAsString(); + xmlHelper.assertSource(content, matcher); + } + }; + } + +} diff --git a/src/main/java/org/springframework/test/web/server/result/CookieResultMatchers.java b/src/main/java/org/springframework/test/web/server/result/CookieResultMatchers.java new file mode 100644 index 0000000..4bb14d2 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/CookieResultMatchers.java @@ -0,0 +1,197 @@ +/* + * 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.server.result; + +import static org.springframework.test.web.AssertionErrors.assertEquals; +import static org.springframework.test.web.AssertionErrors.assertTrue; + +import javax.servlet.http.Cookie; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultMatcher; + +/** + * Factory for response cookie assertions. An instance of this class is + * typically accessed via {@link MockMvcResultMatchers#cookie()}. + * + * @author Rossen Stoyanchev + * @author Thomas Bruyelle + */ +public class CookieResultMatchers { + + + /** + * Protected constructor. + * Use {@link MockMvcResultMatchers#cookie()}. + */ + protected CookieResultMatchers() { + } + + /** + * Assert a cookie value with the given Hamcrest {@link Matcher}. + */ + public ResultMatcher value(final String name, final Matcher super String> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) { + Cookie cookie = result.getResponse().getCookie(name); + assertTrue("Response cookie not found: " + name, cookie != null); + MatcherAssert.assertThat("Response cookie", cookie.getValue(), matcher); + } + }; + } + + /** + * Assert a cookie value. + */ + public ResultMatcher value(String name, String value) { + return value(name, Matchers.equalTo(value)); + } + + /** + * Assert a cookie exists. The existence check is irrespective of whether + * max age is 0 (i.e. expired). + */ + public ResultMatcher exists(final String name) { + return new ResultMatcher() { + public void match(MvcResult result) { + Cookie cookie = result.getResponse().getCookie(name); + assertTrue("No cookie with name: " + name, cookie != null); + } + }; + } + + /** + * Assert a cookie does not exist. Note that the existence check is + * irrespective of whether max age is 0, i.e. expired. + */ + public ResultMatcher doesNotExist(final String name) { + return new ResultMatcher() { + public void match(MvcResult result) { + Cookie cookie = result.getResponse().getCookie(name); + assertTrue("Unexpected cookie with name " + name, cookie == null); + } + }; + } + + /** + * Assert a cookie's maxAge with a Hamcrest {@link Matcher}. + */ + public ResultMatcher maxAge(final String name, final Matcher super Integer> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) { + Cookie cookie = result.getResponse().getCookie(name); + assertTrue("No cookie with name: " + name, cookie != null); + MatcherAssert.assertThat("Response cookie maxAge", cookie.getMaxAge(), matcher); + } + }; + } + + /** + * Assert a cookie's maxAge value. + */ + public ResultMatcher maxAge(String name, int maxAge) { + return maxAge(name, Matchers.equalTo(maxAge)); + } + + /** + * Assert a cookie path with a Hamcrest {@link Matcher}. + */ + public ResultMatcher path(final String name, final Matcher super String> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + Cookie cookie = result.getResponse().getCookie(name); + MatcherAssert.assertThat("Response cookie path", cookie.getPath(), matcher); + } + }; + } + + public ResultMatcher path(String name, String path) { + return path(name, Matchers.equalTo(path)); + } + + /** + * Assert a cookie's domain with a Hamcrest {@link Matcher}. + */ + public ResultMatcher domain(final String name, final Matcher super String> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + Cookie cookie = result.getResponse().getCookie(name); + MatcherAssert.assertThat("Response cookie domain", cookie.getDomain(), matcher); + } + }; + } + + /** + * Assert a cookie's domain value. + */ + public ResultMatcher domain(String name, String domain) { + return domain(name, Matchers.equalTo(domain)); + } + + /** + * Assert a cookie's comment with a Hamcrest {@link Matcher}. + */ + public ResultMatcher comment(final String name, final Matcher super String> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + Cookie cookie = result.getResponse().getCookie(name); + MatcherAssert.assertThat("Response cookie comment", cookie.getComment(), matcher); + } + }; + } + + /** + * Assert a cookie's comment value. + */ + public ResultMatcher comment(String name, String comment) { + return comment(name, Matchers.equalTo(comment)); + } + + /** + * Assert a cookie's version with a Hamcrest {@link Matcher} + */ + public ResultMatcher version(final String name, final Matcher super Integer> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + Cookie cookie = result.getResponse().getCookie(name); + MatcherAssert.assertThat("Response cookie version", cookie.getVersion(), matcher); + } + }; + } + + /** + * Assert a cookie's version value. + */ + public ResultMatcher version(String name, int version) { + return version(name, Matchers.equalTo(version)); + } + + /** + * Assert whether the cookie must be sent over a secure protocol or not. + */ + public ResultMatcher secure(final String name, final boolean secure) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + Cookie cookie = result.getResponse().getCookie(name); + assertEquals("Response cookie secure", secure, cookie.getSecure()); + } + }; + } +} diff --git a/src/main/java/org/springframework/test/web/server/result/FlashAttributeResultMatchers.java b/src/main/java/org/springframework/test/web/server/result/FlashAttributeResultMatchers.java new file mode 100644 index 0000000..ab9ced4 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/FlashAttributeResultMatchers.java @@ -0,0 +1,86 @@ +/* + * Copyright 2011-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.server.result; + +import static org.springframework.test.web.AssertionErrors.*; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultMatcher; + +/** + * Factory for "output" flash attribute assertions. An instance of this class is + * typically accessed via {@link MockMvcResultMatchers#flash()}. + * + * @author Rossen Stoyanchev + */ +public class FlashAttributeResultMatchers { + + + /** + * Protected constructor. + * Use {@link MockMvcResultMatchers#flash()}. + */ + protected FlashAttributeResultMatchers() { + } + + /** + * Assert a flash attribute's value with the given Hamcrest {@link Matcher}. + */ + public
ResultMatcher attribute(final String name, final Matcher matcher) { + return new ResultMatcher() { + @SuppressWarnings("unchecked") + public void match(MvcResult result) throws Exception { + MatcherAssert.assertThat("Flash attribute", (T) result.getFlashMap().get(name), matcher); + } + }; + } + + /** + * Assert a flash attribute's value. + */ + public ResultMatcher attribute(final String name, final Object value) { + return attribute(name, Matchers.equalTo(value)); + } + + /** + * Assert the existence of the given flash attributes. + */ + public ResultMatcher attributeExists(final String... names) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + for (String name : names) { + attribute(name, Matchers.notNullValue()).match(result); + } + } + }; + } + + /** + * Assert the number of flash attributes. + */ + public ResultMatcher attributeCount(final int count) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + assertEquals("FlashMap size", count, result.getFlashMap().size()); + } + }; + } + +} diff --git a/src/main/java/org/springframework/test/web/server/result/HandlerResultMatchers.java b/src/main/java/org/springframework/test/web/server/result/HandlerResultMatchers.java new file mode 100644 index 0000000..b69e7f8 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/HandlerResultMatchers.java @@ -0,0 +1,112 @@ +/* + * Copyright 2011-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.server.result; + +import static org.springframework.test.web.AssertionErrors.assertEquals; +import static org.springframework.test.web.AssertionErrors.assertTrue; + +import java.lang.reflect.Method; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultMatcher; +import org.springframework.util.ClassUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +/** + * Factory for assertions on the selected handler. An instance of this class is + * typically accessed via {@link MockMvcResultMatchers#handler()}. + * + * @author Rossen Stoyanchev + */ +public class HandlerResultMatchers { + + + /** + * Protected constructor. + * Use {@link MockMvcResultMatchers#handler()}. + */ + protected HandlerResultMatchers() { + } + + /** + * Assert the type of the handler that processed the request. + */ + public ResultMatcher handlerType(final Class> type) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + Object handler = result.getHandler(); + assertTrue("No handler: ", handler != null); + Class> actual = handler.getClass(); + if (HandlerMethod.class.isInstance(handler)) { + actual = ((HandlerMethod) handler).getBeanType(); + } + assertEquals("Handler type", type, ClassUtils.getUserClass(actual)); + } + }; + } + + /** + * Assert the name of the controller method that processed the request with + * the given Hamcrest {@link Matcher}. + * + * Use of this method implies annotated controllers are processed with + * {@link RequestMappingHandlerMapping} and {@link RequestMappingHandlerAdapter}. + */ + public ResultMatcher methodName(final Matcher super String> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + Object handler = result.getHandler(); + assertTrue("No handler: ", handler != null); + assertTrue("Not a HandlerMethod: " + handler, HandlerMethod.class.isInstance(handler)); + MatcherAssert.assertThat("HandlerMethod", ((HandlerMethod) handler).getMethod().getName(), matcher); + } + }; + } + + /** + * Assert the name of the controller method that processed the request. + * + *
Use of this method implies annotated controllers are processed with + * {@link RequestMappingHandlerMapping} and {@link RequestMappingHandlerAdapter}. + */ + public ResultMatcher methodName(final String name) { + return methodName(Matchers.equalTo(name)); + } + + /** + * Assert the controller method that processed the request. + * + *
Use of this method implies annotated controllers are processed with + * {@link RequestMappingHandlerMapping} and {@link RequestMappingHandlerAdapter}. + */ + public ResultMatcher method(final Method method) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + Object handler = result.getHandler(); + assertTrue("No handler: ", handler != null); + assertTrue("Not a HandlerMethod: " + handler, HandlerMethod.class.isInstance(handler)); + assertEquals("HandlerMethod", method, ((HandlerMethod) handler).getMethod()); + } + }; + } + +} diff --git a/src/main/java/org/springframework/test/web/server/result/HeaderResultMatchers.java b/src/main/java/org/springframework/test/web/server/result/HeaderResultMatchers.java new file mode 100644 index 0000000..6c5c51c --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/HeaderResultMatchers.java @@ -0,0 +1,72 @@ +/* + * Copyright 2011-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.server.result; + +import static org.springframework.test.web.AssertionErrors.assertEquals; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultMatcher; + +/** + * Factory for response header assertions. An instance of this + * class is usually accessed via {@link MockMvcResultMatchers#header()}. + * + * @author Rossen Stoyanchev + */ +public class HeaderResultMatchers { + + + /** + * Protected constructor. + * Use {@link MockMvcResultMatchers#header()}. + */ + protected HeaderResultMatchers() { + } + + /** + * Assert a response header with the given Hamcrest {@link Matcher}. + */ + public ResultMatcher string(final String name, final Matcher super String> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) { + MatcherAssert.assertThat("Response header", result.getResponse().getHeader(name), matcher); + } + }; + } + + /** + * Assert the primary value of a response header as a {@link String}. + */ + public ResultMatcher string(final String name, final String value) { + return string(name, Matchers.equalTo(value)); + } + + /** + * Assert the primary value of a response header as a {@link Long}. + */ + public ResultMatcher longValue(final String name, final long value) { + return new ResultMatcher() { + public void match(MvcResult result) { + assertEquals("Response header " + name, value, Long.parseLong(result.getResponse().getHeader(name))); + } + }; + } + +} diff --git a/src/main/java/org/springframework/test/web/server/result/JsonPathResultMatchers.java b/src/main/java/org/springframework/test/web/server/result/JsonPathResultMatchers.java new file mode 100644 index 0000000..6ffba3d --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/JsonPathResultMatchers.java @@ -0,0 +1,100 @@ +/* + * Copyright 2011-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.server.result; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; + +import java.util.List; + +import org.hamcrest.Matcher; +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultMatcher; +import org.springframework.test.web.support.JsonPathExpectationsHelper; + +/** + * Factory for assertions on the response content using JSONPath expressions. + * An instance of this class is typically accessed via + * {@link MockMvcResultMatchers#jsonPpath}. + * + * @author Rossen Stoyanchev + */ +public class JsonPathResultMatchers { + + private JsonPathExpectationsHelper jsonPathHelper; + + /** + * Protected constructor. Use + * {@link MockMvcResultMatchers#jsonPath(String, Object...)} or + * {@link MockMvcResultMatchers#jsonPath(String, Matcher)}. + */ + protected JsonPathResultMatchers(String expression, Object ... args) { + this.jsonPathHelper = new JsonPathExpectationsHelper(expression, args); + } + + /** + * Evaluate the JSONPath and assert the value of the content found with the + * given Hamcrest {@code Matcher}. + */ + public
ResultMatcher value(final Matcher matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + String content = result.getResponse().getContentAsString(); + jsonPathHelper.assertValue(content, matcher); + } + }; + } + + /** + * Evaluate the JSONPath and assert the value of the content found. + */ + public ResultMatcher value(Object value) { + return value(equalTo(value)); + } + + /** + * Evaluate the JSONPath and assert that content exists. + */ + public ResultMatcher exists() { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + String content = result.getResponse().getContentAsString(); + jsonPathHelper.exists(content); + } + }; + } + + /** + * Evaluate the JSON path and assert not content was found. + */ + public ResultMatcher doesNotExist() { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + String content = result.getResponse().getContentAsString(); + jsonPathHelper.doesNotExist(content); + } + }; + } + + /** + * Evluate the JSON path and assert the content found is an array. + */ + public ResultMatcher isArray() { + return value(instanceOf(List.class)); + } +} diff --git a/src/main/java/org/springframework/test/web/server/result/MockMvcResultHandlers.java b/src/main/java/org/springframework/test/web/server/result/MockMvcResultHandlers.java new file mode 100644 index 0000000..bdb0d19 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/MockMvcResultHandlers.java @@ -0,0 +1,65 @@ +/* + * Copyright 2011-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.server.result; + +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultHandler; +import org.springframework.util.CollectionUtils; + +/** + * Static, factory methods for {@link ResultHandler}-based result actions. + * + * Eclipse users: consider adding this class as a Java editor + * favorite. To navigate, open the Preferences and type "favorites". + * + * @author Rossen Stoyanchev + */ +public abstract class MockMvcResultHandlers { + + + private MockMvcResultHandlers() { + } + + /** + * Print {@link MvcResult} details to the "standard" output stream. + */ + public static ResultHandler print() { + return new ConsolePrintingResultHandler(); + } + + + /** An {@link PrintingResultHandler} that writes to the "standard" output stream */ + private static class ConsolePrintingResultHandler extends PrintingResultHandler { + + public ConsolePrintingResultHandler() { + super(new ResultValuePrinter() { + + public void printHeading(String heading) { + System.out.println(); + System.out.println(String.format("%20s:", heading)); + } + + public void printValue(String label, Object value) { + if (value != null && value.getClass().isArray()) { + value = CollectionUtils.arrayToList(value); + } + System.out.println(String.format("%20s = %s", label, value)); + } + }); + } + } +} diff --git a/src/main/java/org/springframework/test/web/server/result/MockMvcResultMatchers.java b/src/main/java/org/springframework/test/web/server/result/MockMvcResultMatchers.java new file mode 100644 index 0000000..e964b67 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/MockMvcResultMatchers.java @@ -0,0 +1,184 @@ +/* + * Copyright 2011-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.server.result; + +import static org.springframework.test.web.AssertionErrors.assertEquals; + +import java.util.Map; + +import javax.xml.xpath.XPathExpressionException; + +import org.hamcrest.Matcher; +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultMatcher; + +/** + * Static, factory methods for {@link ResultMatcher}-based result actions. + * + *
Eclipse users: consider adding this class as a Java editor + * favorite. To navigate, open the Preferences and type "favorites". + * + * @author Rossen Stoyanchev + */ +public abstract class MockMvcResultMatchers { + + + private MockMvcResultMatchers() { + } + + /** + * Access to request-related assertions. + */ + public static RequestResultMatchers request() { + return new RequestResultMatchers(); + } + + /** + * Access to assertions for the handler that handled the request. + */ + public static HandlerResultMatchers handler() { + return new HandlerResultMatchers(); + } + + /** + * Access to model-related assertions. + */ + public static ModelResultMatchers model() { + return new ModelResultMatchers(); + } + + /** + * Access to assertions on the selected view. + */ + public static ViewResultMatchers view() { + return new ViewResultMatchers(); + } + + /** + * Access to flash attribute assertions. + */ + public static FlashAttributeResultMatchers flash() { + return new FlashAttributeResultMatchers(); + } + + /** + * Asserts the request was forwarded to the given URL. + */ + public static ResultMatcher forwardedUrl(final String expectedUrl) { + return new ResultMatcher() { + public void match(MvcResult result) { + assertEquals("Forwarded URL", expectedUrl, result.getResponse().getForwardedUrl()); + } + }; + } + + /** + * Asserts the request was redirected to the given URL. + */ + public static ResultMatcher redirectedUrl(final String expectedUrl) { + return new ResultMatcher() { + public void match(MvcResult result) { + assertEquals("Redirected URL", expectedUrl, result.getResponse().getRedirectedUrl()); + } + }; + } + + /** + * Access to response status assertions. + */ + public static StatusResultMatchers status() { + return new StatusResultMatchers(); + } + + /** + * Access to response header assertions. + */ + public static HeaderResultMatchers header() { + return new HeaderResultMatchers(); + } + + /** + * Access to response body assertions. + */ + public static ContentResultMatchers content() { + return new ContentResultMatchers(); + } + + /** + * Access to response body assertions using a JSONPath expression to + * inspect a specific subset of the body. The JSON path expression can be a + * parameterized string using formatting specifiers as defined in + * {@link String#format(String, Object...)}. + * + * @param expression the JSON path optionally parameterized with arguments + * @param args arguments to parameterize the JSON path expression with + */ + public static JsonPathResultMatchers jsonPath(String expression, Object ... args) { + return new JsonPathResultMatchers(expression, args); + } + + /** + * Access to response body assertions using a JSONPath expression to + * inspect a specific subset of the body and a Hamcrest match for asserting + * the value found at the JSON path. + * + * @param expression the JSON path expression + * @param matcher a matcher for the value expected at the JSON path + */ + public static
ResultMatcher jsonPath(String expression, Matcher matcher) { + return new JsonPathResultMatchers(expression).value(matcher); + } + + /** + * Access to response body assertions using an XPath to inspect a specific + * subset of the body. The XPath expression can be a parameterized string + * using formatting specifiers as defined in + * {@link String#format(String, Object...)}. + * + * @param expression the XPath optionally parameterized with arguments + * @param args arguments to parameterize the XPath expression with + */ + public static XpathResultMatchers xpath(String expression, Object... args) throws XPathExpressionException { + return new XpathResultMatchers(expression, null, args); + } + + /** + * Access to response body assertions using an XPath to inspect a specific + * subset of the body. The XPath expression can be a parameterized string + * using formatting specifiers as defined in + * {@link String#format(String, Object...)}. + * + * @param expression the XPath optionally parameterized with arguments + * @param namespaces namespaces referenced in the XPath expression + * @param args arguments to parameterize the XPath expression with + */ + public static XpathResultMatchers xpath(String expression, Map namespaces, Object... args) + throws XPathExpressionException { + + return new XpathResultMatchers(expression, namespaces, args); + } + + /** + * Access to response cookie assertions. + */ + public static CookieResultMatchers cookie() { + return new CookieResultMatchers(); + } + +} diff --git a/src/main/java/org/springframework/test/web/server/result/ModelResultMatchers.java b/src/main/java/org/springframework/test/web/server/result/ModelResultMatchers.java new file mode 100644 index 0000000..675d3c4 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/ModelResultMatchers.java @@ -0,0 +1,174 @@ +/* + * Copyright 2011-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.server.result; + +import static org.springframework.test.web.AssertionErrors.assertEquals; +import static org.springframework.test.web.AssertionErrors.assertTrue; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultMatcher; +import org.springframework.validation.BindingResult; +import org.springframework.web.servlet.ModelAndView; + +/** + * Factory for assertions on the model. An instance of this class is + * typically accessed via {@link MockMvcResultMatchers#model()}. + * + * @author Rossen Stoyanchev + */ +public class ModelResultMatchers { + + + /** + * Protected constructor. + * Use {@link MockMvcResultMatchers#model()}. + */ + protected ModelResultMatchers() { + } + + /** + * Assert a model attribute value with the given Hamcrest {@link Matcher}. + */ + public ResultMatcher attribute(final String name, final Matcher matcher) { + return new ResultMatcher() { + @SuppressWarnings("unchecked") + public void match(MvcResult result) throws Exception { + ModelAndView mav = result.getModelAndView(); + assertTrue("No ModelAndView found", mav != null); + MatcherAssert.assertThat("Model attribute '" + name + "'", (T) mav.getModel().get(name), matcher); + } + }; + } + + /** + * Assert a model attribute value. + */ + public ResultMatcher attribute(String name, Object value) { + return attribute(name, Matchers.equalTo(value)); + } + + /** + * Assert the given model attributes exist. + */ + public ResultMatcher attributeExists(final String... names) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + assertTrue("No ModelAndView found", result.getModelAndView() != null); + for (String name : names) { + attribute(name, Matchers.notNullValue()).match(result); + } + } + }; + } + + /** + * Assert the given model attribute(s) have errors. + */ + public ResultMatcher attributeHasErrors(final String... names) { + return new ResultMatcher() { + public void match(MvcResult mvcResult) throws Exception { + ModelAndView mav = getModelAndView(mvcResult); + for (String name : names) { + BindingResult result = getBindingResult(mav, name); + assertTrue("No errors for attribute: " + name, result.hasErrors()); + } + } + }; + } + + /** + * Assert the given model attribute(s) do not have errors. + */ + public ResultMatcher attributeHasNoErrors(final String... names) { + return new ResultMatcher() { + public void match(MvcResult mvcResult) throws Exception { + ModelAndView mav = getModelAndView(mvcResult); + for (String name : names) { + BindingResult result = getBindingResult(mav, name); + assertTrue("No errors for attribute: " + name, !result.hasErrors()); + } + } + }; + } + + /** + * Assert the given model attribute field(s) have errors. + */ + public ResultMatcher attributeHasFieldErrors(final String name, final String... fieldNames) { + return new ResultMatcher() { + public void match(MvcResult mvcResult) throws Exception { + ModelAndView mav = getModelAndView(mvcResult); + BindingResult result = getBindingResult(mav, name); + assertTrue("No errors for attribute: '" + name + "'", result.hasErrors()); + for (final String fieldName : fieldNames) { + assertTrue("No errors for field: '" + fieldName + "' of attribute: " + name, + result.hasFieldErrors(fieldName)); + } + } + }; + } + + /** + * Assert the model has no errors. + */ + public ResultMatcher hasNoErrors() { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + ModelAndView mav = getModelAndView(result); + for (Object value : mav.getModel().values()) { + if (value instanceof BindingResult) { + assertTrue("Unexpected binding error(s): " + value, !((BindingResult) value).hasErrors()); + } + } + } + }; + } + + /** + * Assert the number of model attributes. + */ + public ResultMatcher size(final int size) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + ModelAndView mav = getModelAndView(result); + int actual = 0; + for (String key : mav.getModel().keySet()) { + if (!key.startsWith(BindingResult.MODEL_KEY_PREFIX)) { + actual++; + } + } + assertEquals("Model size", size, actual); + } + }; + } + + private ModelAndView getModelAndView(MvcResult mvcResult) { + ModelAndView mav = mvcResult.getModelAndView(); + assertTrue("No ModelAndView found", mav != null); + return mav; + } + + private BindingResult getBindingResult(ModelAndView mav, String name) { + BindingResult result = (BindingResult) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + name); + assertTrue("No BindingResult for attribute: " + name, result != null); + return result; + } + +} diff --git a/src/main/java/org/springframework/test/web/server/result/PrintingResultHandler.java b/src/main/java/org/springframework/test/web/server/result/PrintingResultHandler.java new file mode 100644 index 0000000..d71d135 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/PrintingResultHandler.java @@ -0,0 +1,199 @@ +/* + * Copyright 2011-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.server.result; + +import java.util.Enumeration; + +import org.springframework.http.HttpHeaders; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultHandler; +import org.springframework.validation.BindingResult; +import org.springframework.validation.Errors; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.FlashMap; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.support.RequestContextUtils; + +/** + * Result handler that prints {@link MvcResult} details to the "standard" output + * stream. An instance of this class is typically accessed via + * {@link MockMvcResultHandlers#print()}. + * + * @author Rossen Stoyanchev + */ +public class PrintingResultHandler implements ResultHandler { + + private final ResultValuePrinter printer; + + + /** + * Protected constructor. + * @param printer a {@link ResultValuePrinter} to do the actual writing + */ + protected PrintingResultHandler(ResultValuePrinter printer) { + this.printer = printer; + } + + /** + * @return the result value printer. + */ + protected ResultValuePrinter getPrinter() { + return this.printer; + } + + /** + * Print {@link MvcResult} details to the "standard" output stream. + */ + public final void handle(MvcResult result) throws Exception { + + this.printer.printHeading("MockHttpServletRequest"); + printRequest(result.getRequest()); + + this.printer.printHeading("Handler"); + printHandler(result.getHandler(), result.getInterceptors()); + + this.printer.printHeading("Resolved Exception"); + printResolvedException(result.getResolvedException()); + + this.printer.printHeading("ModelAndView"); + printModelAndView(result.getModelAndView()); + + this.printer.printHeading("FlashMap"); + printFlashMap(RequestContextUtils.getOutputFlashMap(result.getRequest())); + + this.printer.printHeading("MockHttpServletResponse"); + printResponse(result.getResponse()); + } + + /** Print the request */ + protected void printRequest(MockHttpServletRequest request) throws Exception { + this.printer.printValue("HTTP Method", request.getMethod()); + this.printer.printValue("Request URI", request.getRequestURI()); + this.printer.printValue("Parameters", request.getParameterMap()); + this.printer.printValue("Headers", getRequestHeaders(request)); + } + + protected static HttpHeaders getRequestHeaders(MockHttpServletRequest request) { + HttpHeaders headers = new HttpHeaders(); + Enumeration> names = request.getHeaderNames(); + while (names.hasMoreElements()) { + String name = (String) names.nextElement(); + Enumeration values = request.getHeaders(name); + while (values.hasMoreElements()) { + headers.add(name, values.nextElement()); + } + } + return headers; + } + + /** Print the handler */ + protected void printHandler(Object handler, HandlerInterceptor[] interceptors) throws Exception { + if (handler == null) { + this.printer.printValue("Type", null); + } + else { + if (handler instanceof HandlerMethod) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + this.printer.printValue("Type", handlerMethod.getBeanType().getName()); + this.printer.printValue("Method", handlerMethod); + } + else { + this.printer.printValue("Type", handler.getClass().getName()); + } + } + } + + /** Print exceptions resolved through a HandlerExceptionResolver */ + protected void printResolvedException(Exception resolvedException) throws Exception { + if (resolvedException == null) { + this.printer.printValue("Type", null); + } + else { + this.printer.printValue("Type", resolvedException.getClass().getName()); + } + } + + /** Print the ModelAndView */ + protected void printModelAndView(ModelAndView mav) throws Exception { + this.printer.printValue("View name", (mav != null) ? mav.getViewName() : null); + this.printer.printValue("View", (mav != null) ? mav.getView() : null); + if (mav == null || mav.getModel().size() == 0) { + this.printer.printValue("Model", null); + } + else { + for (String name : mav.getModel().keySet()) { + if (!name.startsWith(BindingResult.MODEL_KEY_PREFIX)) { + Object value = mav.getModel().get(name); + this.printer.printValue("Attribute", name); + this.printer.printValue("value", value); + Errors errors = (Errors) mav.getModel().get(BindingResult.MODEL_KEY_PREFIX + name); + if (errors != null) { + this.printer.printValue("errors", errors.getAllErrors()); + } + } + } + } + } + + /** Print "output" flash attributes */ + protected void printFlashMap(FlashMap flashMap) throws Exception { + if (flashMap == null) { + this.printer.printValue("Attributes", null); + } + else { + for (String name : flashMap.keySet()) { + this.printer.printValue("Attribute", name); + this.printer.printValue("value", flashMap.get(name)); + } + } + } + + /** Print the response */ + protected void printResponse(MockHttpServletResponse response) throws Exception { + this.printer.printValue("Status", response.getStatus()); + this.printer.printValue("Error message", response.getErrorMessage()); + this.printer.printValue("Headers", getResponseHeaders(response)); + this.printer.printValue("Content type", response.getContentType()); + this.printer.printValue("Body", response.getContentAsString()); + this.printer.printValue("Forwarded URL", response.getForwardedUrl()); + this.printer.printValue("Redirected URL", response.getRedirectedUrl()); + this.printer.printValue("Cookies", response.getCookies()); + } + + protected static HttpHeaders getResponseHeaders(MockHttpServletResponse response) { + HttpHeaders headers = new HttpHeaders(); + for (String name : response.getHeaderNames()) { + headers.put(name, response.getHeaders(name)); + } + return headers; + } + + + /** + * A contract for how to actually write result information. + */ + protected interface ResultValuePrinter { + + void printHeading(String heading); + + void printValue(String label, Object value); + } + +} diff --git a/src/main/java/org/springframework/test/web/server/result/RequestResultMatchers.java b/src/main/java/org/springframework/test/web/server/result/RequestResultMatchers.java new file mode 100644 index 0000000..29d35f1 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/RequestResultMatchers.java @@ -0,0 +1,81 @@ +/* + * Copyright 2011-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.server.result; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultMatcher; + +/** + * Factory for assertions on the request. An instance of this class is + * typically accessed via {@link MockMvcResultMatchers#request()}. + * + * @author Rossen Stoyanchev + */ +public class RequestResultMatchers { + + + /** + * Protected constructor. + * Use {@link MockMvcResultMatchers#request()}. + */ + protected RequestResultMatchers() { + } + + /** + * Assert a request attribute value with the given Hamcrest {@link Matcher}. + */ + public ResultMatcher attribute(final String name, final Matcher matcher) { + return new ResultMatcher() { + @SuppressWarnings("unchecked") + public void match(MvcResult result) { + T value = (T) result.getRequest().getAttribute(name); + MatcherAssert.assertThat("Request attribute: ", value, matcher); + } + }; + } + + /** + * Assert a request attribute value. + */ + public ResultMatcher attribute(String name, Object value) { + return attribute(name, Matchers.equalTo(value)); + } + + /** + * Assert a session attribute value with the given Hamcrest {@link Matcher}. + */ + public ResultMatcher sessionAttribute(final String name, final Matcher matcher) { + return new ResultMatcher() { + @SuppressWarnings("unchecked") + public void match(MvcResult result) { + T value = (T) result.getRequest().getSession().getAttribute(name); + MatcherAssert.assertThat("Request attribute: ", value, matcher); + } + }; + } + + /** + * Assert a session attribute value.. + */ + public ResultMatcher sessionAttribute(String name, Object value) { + return sessionAttribute(name, Matchers.equalTo(value)); + } + +} diff --git a/src/main/java/org/springframework/test/web/server/result/StatusResultMatchers.java b/src/main/java/org/springframework/test/web/server/result/StatusResultMatchers.java new file mode 100644 index 0000000..c0f6241 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/StatusResultMatchers.java @@ -0,0 +1,540 @@ +/* + * Copyright 2011-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.server.result; + +import static org.springframework.test.web.AssertionErrors.assertEquals; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultMatcher; + +/** + * Factory for assertions on the response status. An instance of this class is + * typically accessed via {@link MockMvcResultMatchers#status()}. + * + * @author Keesun Baik + * @author Rossen Stoyanchev + */ +public class StatusResultMatchers { + + + /** + * Protected constructor. + * Use {@link MockMvcResultMatchers#status()}. + */ + protected StatusResultMatchers() { + } + + /** + * Assert the response status code with the given Hamcrest {@link Matcher}. + */ + public ResultMatcher is(final Matcher matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + MatcherAssert.assertThat("Status: ", result.getResponse().getStatus(), matcher); + } + }; + } + + /** + * Assert the response status code is equal to an integer value. + */ + public ResultMatcher is(int status) { + return is(Matchers.equalTo(status)); + } + + + /** + * Assert the Servlet response error message with the given Hamcrest {@link Matcher}. + */ + public ResultMatcher reason(final Matcher super String> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + MatcherAssert.assertThat("Status reason: ", result.getResponse().getErrorMessage(), matcher); + } + }; + } + + /** + * Assert the Servlet response error message. + */ + public ResultMatcher reason(String reason) { + return reason(Matchers.equalTo(reason)); + } + + /** + * Assert the response status code is {@code HttpStatus.CONTINUE} (100). + */ + public ResultMatcher isContinue() { + return matcher(HttpStatus.CONTINUE); + } + + /** + * Assert the response status code is {@code HttpStatus.SWITCHING_PROTOCOLS} (101). + */ + public ResultMatcher isSwitchingProtocols() { + return matcher(HttpStatus.SWITCHING_PROTOCOLS); + } + + /** + * Assert the response status code is {@code HttpStatus.PROCESSING} (102). + */ + public ResultMatcher isProcessing() { + return matcher(HttpStatus.PROCESSING); + } + + /** + * Assert the response status code is {@code HttpStatus.CHECKPOINT} (103). + */ + public ResultMatcher isCheckpoint() { + return matcher(HttpStatus.valueOf(103)); + } + + /** + * Assert the response status code is {@code HttpStatus.OK} (200). + */ + public ResultMatcher isOk() { + return matcher(HttpStatus.OK); + } + + /** + * Assert the response status code is {@code HttpStatus.CREATED} (201). + */ + public ResultMatcher isCreated() { + return matcher(HttpStatus.CREATED); + } + + /** + * Assert the response status code is {@code HttpStatus.ACCEPTED} (202). + */ + public ResultMatcher isAccepted() { + return matcher(HttpStatus.ACCEPTED); + } + + /** + * Assert the response status code is {@code HttpStatus.NON_AUTHORITATIVE_INFORMATION} (203). + */ + public ResultMatcher isNonAuthoritativeInformation() { + return matcher(HttpStatus.NON_AUTHORITATIVE_INFORMATION); + } + + /** + * Assert the response status code is {@code HttpStatus.NO_CONTENT} (204). + */ + public ResultMatcher isNoContent() { + return matcher(HttpStatus.NO_CONTENT); + } + + /** + * Assert the response status code is {@code HttpStatus.RESET_CONTENT} (205). + */ + public ResultMatcher isResetContent() { + return matcher(HttpStatus.RESET_CONTENT); + } + + /** + * Assert the response status code is {@code HttpStatus.PARTIAL_CONTENT} (206). + */ + public ResultMatcher isPartialContent() { + return matcher(HttpStatus.PARTIAL_CONTENT); + } + + /** + * Assert the response status code is {@code HttpStatus.MULTI_STATUS} (207). + */ + public ResultMatcher isMultiStatus() { + return matcher(HttpStatus.MULTI_STATUS); + } + + /** + * Assert the response status code is {@code HttpStatus.ALREADY_REPORTED} (208). + */ + public ResultMatcher isAlreadyReported() { + return matcher(HttpStatus.ALREADY_REPORTED); + } + + /** + * Assert the response status code is {@code HttpStatus.IM_USED} (226). + */ + public ResultMatcher isImUsed() { + return matcher(HttpStatus.IM_USED); + } + + /** + * Assert the response status code is {@code HttpStatus.MULTIPLE_CHOICES} (300). + */ + public ResultMatcher isMultipleChoices() { + return matcher(HttpStatus.MULTIPLE_CHOICES); + } + + /** + * Assert the response status code is {@code HttpStatus.MOVED_PERMANENTLY} (301). + */ + public ResultMatcher isMovedPermanently() { + return matcher(HttpStatus.MOVED_PERMANENTLY); + } + + /** + * Assert the response status code is {@code HttpStatus.FOUND} (302). + */ + public ResultMatcher isFound() { + return matcher(HttpStatus.FOUND); + } + + /** + * Assert the response status code is {@code HttpStatus.MOVED_TEMPORARILY} (302). + */ + public ResultMatcher isMovedTemporarily() { + return matcher(HttpStatus.MOVED_TEMPORARILY); + } + + /** + * Assert the response status code is {@code HttpStatus.SEE_OTHER} (303). + */ + public ResultMatcher isSeeOther() { + return matcher(HttpStatus.SEE_OTHER); + } + + /** + * Assert the response status code is {@code HttpStatus.NOT_MODIFIED} (304). + */ + public ResultMatcher isNotModified() { + return matcher(HttpStatus.NOT_MODIFIED); + } + + /** + * Assert the response status code is {@code HttpStatus.USE_PROXY} (305). + */ + public ResultMatcher isUseProxy() { + return matcher(HttpStatus.USE_PROXY); + } + + /** + * Assert the response status code is {@code HttpStatus.TEMPORARY_REDIRECT} (307). + */ + public ResultMatcher isTemporaryRedirect() { + return matcher(HttpStatus.TEMPORARY_REDIRECT); + } + + /** + * Assert the response status code is {@code HttpStatus.RESUME_INCOMPLETE} (308). + */ + public ResultMatcher isResumeIncomplete() { + return matcher(HttpStatus.valueOf(308)); + } + + /** + * Assert the response status code is {@code HttpStatus.BAD_REQUEST} (400). + */ + public ResultMatcher isBadRequest() { + return matcher(HttpStatus.BAD_REQUEST); + } + + /** + * Assert the response status code is {@code HttpStatus.UNAUTHORIZED} (401). + */ + public ResultMatcher isUnauthorized() { + return matcher(HttpStatus.UNAUTHORIZED); + } + + /** + * Assert the response status code is {@code HttpStatus.PAYMENT_REQUIRED} (402). + */ + public ResultMatcher isPaymentRequired() { + return matcher(HttpStatus.PAYMENT_REQUIRED); + } + + /** + * Assert the response status code is {@code HttpStatus.FORBIDDEN} (403). + */ + public ResultMatcher isForbidden() { + return matcher(HttpStatus.FORBIDDEN); + } + + /** + * Assert the response status code is {@code HttpStatus.NOT_FOUND} (404). + */ + public ResultMatcher isNotFound() { + return matcher(HttpStatus.NOT_FOUND); + } + + /** + * Assert the response status code is {@code HttpStatus.METHOD_NOT_ALLOWED} (405). + */ + public ResultMatcher isMethodNotAllowed() { + return matcher(HttpStatus.METHOD_NOT_ALLOWED); + } + + /** + * Assert the response status code is {@code HttpStatus.NOT_ACCEPTABLE} (406). + */ + public ResultMatcher isNotAcceptable() { + return matcher(HttpStatus.NOT_ACCEPTABLE); + } + + /** + * Assert the response status code is {@code HttpStatus.PROXY_AUTHENTICATION_REQUIRED} (407). + */ + public ResultMatcher isProxyAuthenticationRequired() { + return matcher(HttpStatus.PROXY_AUTHENTICATION_REQUIRED); + } + + /** + * Assert the response status code is {@code HttpStatus.REQUEST_TIMEOUT} (408). + */ + public ResultMatcher isRequestTimeout() { + return matcher(HttpStatus.REQUEST_TIMEOUT); + } + + /** + * Assert the response status code is {@code HttpStatus.CONFLICT} (409). + */ + public ResultMatcher isConflict() { + return matcher(HttpStatus.CONFLICT); + } + + /** + * Assert the response status code is {@code HttpStatus.GONE} (410). + */ + public ResultMatcher isGone() { + return matcher(HttpStatus.GONE); + } + + /** + * Assert the response status code is {@code HttpStatus.LENGTH_REQUIRED} (411). + */ + public ResultMatcher isLengthRequired() { + return matcher(HttpStatus.LENGTH_REQUIRED); + } + + /** + * Assert the response status code is {@code HttpStatus.PRECONDITION_FAILED} (412). + */ + public ResultMatcher isPreconditionFailed() { + return matcher(HttpStatus.PRECONDITION_FAILED); + } + + /** + * Assert the response status code is {@code HttpStatus.REQUEST_ENTITY_TOO_LARGE} (413). + */ + public ResultMatcher isRequestEntityTooLarge() { + return matcher(HttpStatus.REQUEST_ENTITY_TOO_LARGE); + } + + /** + * Assert the response status code is {@code HttpStatus.REQUEST_URI_TOO_LONG} (414). + */ + public ResultMatcher isRequestUriTooLong() { + return matcher(HttpStatus.REQUEST_URI_TOO_LONG); + } + + /** + * Assert the response status code is {@code HttpStatus.UNSUPPORTED_MEDIA_TYPE} (415). + */ + public ResultMatcher isUnsupportedMediaType() { + return matcher(HttpStatus.UNSUPPORTED_MEDIA_TYPE); + } + + /** + * Assert the response status code is {@code HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE} (416). + */ + public ResultMatcher isRequestedRangeNotSatisfiable() { + return matcher(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE); + } + + /** + * Assert the response status code is {@code HttpStatus.EXPECTATION_FAILED} (417). + */ + public ResultMatcher isExpectationFailed() { + return matcher(HttpStatus.EXPECTATION_FAILED); + } + + /** + * Assert the response status code is {@code HttpStatus.I_AM_A_TEAPOT} (418). + */ + public ResultMatcher isIAmATeapot() { + return matcher(HttpStatus.valueOf(418)); + } + + /** + * Assert the response status code is {@code HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE} (419). + */ + public ResultMatcher isInsufficientSpaceOnResource() { + return matcher(HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE); + } + + /** + * Assert the response status code is {@code HttpStatus.METHOD_FAILURE} (420). + */ + public ResultMatcher isMethodFailure() { + return matcher(HttpStatus.METHOD_FAILURE); + } + + /** + * Assert the response status code is {@code HttpStatus.DESTINATION_LOCKED} (421). + */ + public ResultMatcher isDestinationLocked() { + return matcher(HttpStatus.DESTINATION_LOCKED); + } + + /** + * Assert the response status code is {@code HttpStatus.UNPROCESSABLE_ENTITY} (422). + */ + public ResultMatcher isUnprocessableEntity() { + return matcher(HttpStatus.UNPROCESSABLE_ENTITY); + } + + /** + * Assert the response status code is {@code HttpStatus.LOCKED} (423). + */ + public ResultMatcher isLocked() { + return matcher(HttpStatus.LOCKED); + } + + /** + * Assert the response status code is {@code HttpStatus.FAILED_DEPENDENCY} (424). + */ + public ResultMatcher isFailedDependency() { + return matcher(HttpStatus.FAILED_DEPENDENCY); + } + + /** + * Assert the response status code is {@code HttpStatus.UPGRADE_REQUIRED} (426). + */ + public ResultMatcher isUpgradeRequired() { + return matcher(HttpStatus.UPGRADE_REQUIRED); + } + + /** + * Assert the response status code is {@code HttpStatus.PRECONDITION_REQUIRED} (428). + */ + public ResultMatcher isPreconditionRequired() { + return matcher(HttpStatus.valueOf(428)); + } + + /** + * Assert the response status code is {@code HttpStatus.TOO_MANY_REQUESTS} (429). + */ + public ResultMatcher isTooManyRequests() { + return matcher(HttpStatus.valueOf(429)); + } + + /** + * Assert the response status code is {@code HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE} (431). + */ + public ResultMatcher isRequestHeaderFieldsTooLarge() { + return matcher(HttpStatus.valueOf(431)); + } + + /** + * Assert the response status code is {@code HttpStatus.INTERNAL_SERVER_ERROR} (500). + */ + public ResultMatcher isInternalServerError() { + return matcher(HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * Assert the response status code is {@code HttpStatus.NOT_IMPLEMENTED} (501). + */ + public ResultMatcher isNotImplemented() { + return matcher(HttpStatus.NOT_IMPLEMENTED); + } + + /** + * Assert the response status code is {@code HttpStatus.BAD_GATEWAY} (502). + */ + public ResultMatcher isBadGateway() { + return matcher(HttpStatus.BAD_GATEWAY); + } + + /** + * Assert the response status code is {@code HttpStatus.SERVICE_UNAVAILABLE} (503). + */ + public ResultMatcher isServiceUnavailable() { + return matcher(HttpStatus.SERVICE_UNAVAILABLE); + } + + /** + * Assert the response status code is {@code HttpStatus.GATEWAY_TIMEOUT} (504). + */ + public ResultMatcher isGatewayTimeout() { + return matcher(HttpStatus.GATEWAY_TIMEOUT); + } + + /** + * Assert the response status code is {@code HttpStatus.HTTP_VERSION_NOT_SUPPORTED} (505). + */ + public ResultMatcher isHttpVersionNotSupported() { + return matcher(HttpStatus.HTTP_VERSION_NOT_SUPPORTED); + } + + /** + * Assert the response status code is {@code HttpStatus.VARIANT_ALSO_NEGOTIATES} (506). + */ + public ResultMatcher isVariantAlsoNegotiates() { + return matcher(HttpStatus.VARIANT_ALSO_NEGOTIATES); + } + + /** + * Assert the response status code is {@code HttpStatus.INSUFFICIENT_STORAGE} (507). + */ + public ResultMatcher isInsufficientStorage() { + return matcher(HttpStatus.INSUFFICIENT_STORAGE); + } + + /** + * Assert the response status code is {@code HttpStatus.LOOP_DETECTED} (508). + */ + public ResultMatcher isLoopDetected() { + return matcher(HttpStatus.LOOP_DETECTED); + } + + /** + * Assert the response status code is {@code HttpStatus.BANDWIDTH_LIMIT_EXCEEDED} (509). + */ + public ResultMatcher isBandwidthLimitExceeded() { + return matcher(HttpStatus.valueOf(509)); + } + + /** + * Assert the response status code is {@code HttpStatus.NOT_EXTENDED} (510). + */ + public ResultMatcher isNotExtended() { + return matcher(HttpStatus.NOT_EXTENDED); + } + + /** + * Assert the response status code is {@code HttpStatus.NETWORK_AUTHENTICATION_REQUIRED} (511). + */ + public ResultMatcher isNetworkAuthenticationRequired() { + return matcher(HttpStatus.valueOf(511)); + } + + /** + * Match the expected response status to that of the HttpServletResponse + */ + private ResultMatcher matcher(final HttpStatus status) { + return new ResultMatcher() { + public void match(MvcResult result) { + assertEquals("Status", status.value(), result.getResponse().getStatus()); + } + }; + } + +} diff --git a/src/main/java/org/springframework/test/web/server/result/ViewResultMatchers.java b/src/main/java/org/springframework/test/web/server/result/ViewResultMatchers.java new file mode 100644 index 0000000..39a081e --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/ViewResultMatchers.java @@ -0,0 +1,62 @@ +/* + * Copyright 2011-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.server.result; + +import static org.springframework.test.web.AssertionErrors.assertTrue; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultMatcher; +import org.springframework.web.servlet.ModelAndView; + +/** + * Factory for assertions on the selected view. An instance of this class is + * typically accessed via {@link MockMvcResultMatchers#view()}. + */ +public class ViewResultMatchers { + + + /** + * Protected constructor. + * Use {@link MockMvcResultMatchers#view()}. + */ + protected ViewResultMatchers() { + } + + /** + * Assert the selected view name with the given Hamcrest {@link Matcher}. + */ + public ResultMatcher name(final Matcher super String> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + ModelAndView mav = result.getModelAndView(); + assertTrue("No ModelAndView found", mav != null); + MatcherAssert.assertThat("View name", mav.getViewName(), matcher); + } + }; + } + + /** + * Assert the selected view name. + */ + public ResultMatcher name(final String name) { + return name(Matchers.equalTo(name)); + } + +} diff --git a/src/main/java/org/springframework/test/web/server/result/XpathResultMatchers.java b/src/main/java/org/springframework/test/web/server/result/XpathResultMatchers.java new file mode 100644 index 0000000..f2d2fee --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/XpathResultMatchers.java @@ -0,0 +1,159 @@ +/* + * Copyright 2011-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.server.result; + +import java.util.Map; + +import javax.xml.xpath.XPathExpressionException; + +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.springframework.test.web.server.MvcResult; +import org.springframework.test.web.server.ResultMatcher; +import org.springframework.test.web.support.XpathExpectationsHelper; +import org.w3c.dom.Node; + +/** + * Factory for response content {@code ResultMatcher}'s using an XPath + * expression. An instance of this class is typically accessed via + * {@link MockMvcResultMatchers#xpath}. + * + * @author Rossen Stoyanchev + */ +public class XpathResultMatchers { + + private final XpathExpectationsHelper xpathHelper; + + + /** + * Protected constructor, not for direct instantiation. Use + * {@link MockMvcResultMatchers#xpath(String, Object...)} or + * {@link MockMvcResultMatchers#xpath(String, Map, Object...)}. + * + * @param expression the XPath expression + * @param namespaces XML namespaces referenced in the XPath expression, or {@code null} + * @param args arguments to parameterize the XPath expression with using the + * formatting specifiers defined in {@link String#format(String, Object...)} + * + * @throws XPathExpressionException + */ + protected XpathResultMatchers(String expression, Map namespaces, Object ... args) + throws XPathExpressionException { + + this.xpathHelper = new XpathExpectationsHelper(expression, namespaces, args); + } + + /** + * Evaluate the XPath and assert the {@link Node} content found with the + * given Hamcrest {@link Matcher}. + */ + public ResultMatcher node(final Matcher super Node> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + String content = result.getResponse().getContentAsString(); + xpathHelper.assertNode(content, matcher); + } + }; + } + + /** + * Evaluate the XPath and assert that content exists. + */ + public ResultMatcher exists() { + return node(Matchers.notNullValue()); + } + + /** + * Evaluate the XPath and assert that content doesn't exist. + */ + public ResultMatcher doesNotExist() { + return node(Matchers.nullValue()); + } + + /** + * Evaluate the XPath and assert the number of nodes found with the given + * Hamcrest {@link Matcher}. + */ + public ResultMatcher nodeCount(final Matcher matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + String content = result.getResponse().getContentAsString(); + xpathHelper.assertNodeCount(content, matcher); + } + }; + } + + /** + * Evaluate the XPath and assert the number of nodes found. + */ + public ResultMatcher nodeCount(int count) { + return nodeCount(Matchers.equalTo(count)); + } + + /** + * Apply the XPath and assert the {@link String} value found with the given + * Hamcrest {@link Matcher}. + */ + public ResultMatcher string(final Matcher super String> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + String content = result.getResponse().getContentAsString(); + xpathHelper.assertString(content, matcher); + } + }; + } + + /** + * Apply the XPath and assert the {@link String} value found. + */ + public ResultMatcher string(String value) { + return string(Matchers.equalTo(value)); + } + + /** + * Evaluate the XPath and assert the {@link Double} value found with the + * given Hamcrest {@link Matcher}. + */ + public ResultMatcher number(final Matcher super Double> matcher) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + String content = result.getResponse().getContentAsString(); + xpathHelper.assertNumber(content, matcher); + } + }; + } + + /** + * Evaluate the XPath and assert the {@link Double} value found. + */ + public ResultMatcher number(Double value) { + return number(Matchers.equalTo(value)); + } + + /** + * Evaluate the XPath and assert the {@link Boolean} value found. + */ + public ResultMatcher booleanValue(final Boolean value) { + return new ResultMatcher() { + public void match(MvcResult result) throws Exception { + String content = result.getResponse().getContentAsString(); + xpathHelper.assertBoolean(content, value); + } + }; + } + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/test/web/server/result/package-info.java b/src/main/java/org/springframework/test/web/server/result/package-info.java new file mode 100644 index 0000000..3ccba20 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/result/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2011-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. + */ + +/** + * Contains built-in {@code ResultMatcher} and {@code ResultHandler} implementations. + * Use {@link org.springframework.test.web.server.result.MockMvcResultMatchers} + * and {@link org.springframework.test.web.server.result.MockMvcResultHandlers} + * to access to instances of those implementations. + */ +package org.springframework.test.web.server.result; diff --git a/src/main/java/org/springframework/test/web/server/setup/AbstractMockMvcBuilder.java b/src/main/java/org/springframework/test/web/server/setup/AbstractMockMvcBuilder.java new file mode 100644 index 0000000..e90db9d --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/setup/AbstractMockMvcBuilder.java @@ -0,0 +1,195 @@ +/* + * Copyright 2011-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.server.setup; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.Filter; +import javax.servlet.ServletContext; + +import org.springframework.mock.web.MockServletConfig; +import org.springframework.test.web.server.MockMvcBuilder; +import org.springframework.test.web.server.MockMvcBuilderSupport; +import org.springframework.test.web.server.MockMvc; +import org.springframework.test.web.server.RequestBuilder; +import org.springframework.test.web.server.ResultHandler; +import org.springframework.test.web.server.ResultMatcher; +import org.springframework.util.Assert; +import org.springframework.web.context.WebApplicationContext; + +/** + * Abstract implementation of {@link MockMvcBuilder} that implements the actual + * {@code build} method, provides convenient methods for configuring filters, + * default request properties, and global expectations, and delegates to an + * abstract method to obtain a {@link WebApplicationContext}. + * + * @author Rossen Stoyanchev + * @author Rob Winch + */ +public abstract class AbstractMockMvcBuilder > + extends MockMvcBuilderSupport implements MockMvcBuilder { + + private List filters = new ArrayList (); + + private RequestBuilder defaultRequestBuilder; + + private final List globalResultMatchers = new ArrayList (); + + private final List globalResultHandlers = new ArrayList (); + + + /** + * Add filters mapped to any request (i.e. "/*"). For example: + * + * + * mockMvcBuilder.addFilters(springSecurityFilterChain); + *+ * + *is the equivalent of the following web.xml configuration: + * + *
+ * <filter-mapping> + * <filter-name>springSecurityFilterChain</filter-name> + * <url-pattern>/*</url-pattern> + * </filter-mapping> + *+ * + *Filters will be invoked in the order in which they are provided. + * + * @param filters the filters to add + */ + @SuppressWarnings("unchecked") + public final
T addFilters(Filter... filters) { + Assert.notNull(filters, "filters cannot be null"); + + for(Filter f : filters) { + Assert.notNull(f, "filters cannot contain null values"); + this.filters.add(f); + } + return (T) this; + } + + /** + * Add a filter mapped to a specific set of patterns. For example: + * + * + * mockMvcBuilder.addFilters(myResourceFilter, "/resources/*"); + *+ * + *is the equivalent of: + * + *
+ * <filter-mapping> + * <filter-name>myResourceFilter</filter-name> + * <url-pattern>/resources/*</url-pattern> + * </filter-mapping> + *+ * + *Filters will be invoked in the order in which they are provided. + * + * @param filter the filter to add + * @param urlPatterns URL patterns to map to; if empty, "/*" is used by default + * @return + */ + @SuppressWarnings("unchecked") + public final
T addFilter(Filter filter, String... urlPatterns) { + + Assert.notNull(filter, "filter cannot be null"); + Assert.notNull(urlPatterns, "urlPatterns cannot be null"); + + if(urlPatterns.length > 0) { + filter = new PatternMappingFilterProxy(filter, urlPatterns); + } + + this.filters.add(filter); + return (T) this; + } + + /** + * Define default request properties that should be merged into all + * performed requests. In effect this provides a mechanism for defining + * common initialization for all requests such as the content type, request + * parameters, session attributes, and any other request property. + * + * Properties specified at the time of performing a request override the + * default properties defined here. + * + * @param requestBuilder a RequestBuilder; see static factory methods in + * {@link org.springframework.test.web.server.request.MockMvcRequestBuilders} + * . + */ + @SuppressWarnings("unchecked") + public final
T defaultRequest(RequestBuilder requestBuilder) { + this.defaultRequestBuilder = requestBuilder; + return (T) this; + } + + /** + * Define a global expectation that should always be applied to + * every response. For example, status code 200 (OK), content type + * {@code "application/json"}, etc. + * + * @param resultMatcher a ResultMatcher; see static factory methods in + * {@link org.springframework.test.web.server.result.MockMvcResultMatchers} + */ + @SuppressWarnings("unchecked") + public final T alwaysExpect(ResultMatcher resultMatcher) { + this.globalResultMatchers.add(resultMatcher); + return (T) this; + } + + /** + * Define a global action that should always be applied to every + * response. For example, writing detailed information about the performed + * request and resulting response to {@code System.out}. + * + * @param resultHandler a ResultHandler; see static factory methods in + * {@link org.springframework.test.web.server.result.MockMvcResultHandlers} + */ + @SuppressWarnings("unchecked") + public final T alwaysDo(ResultHandler resultHandler) { + this.globalResultHandlers.add(resultHandler); + return (T) this; + } + + /** + * Build a {@link MockMvc} instance. + */ + public final MockMvc build() { + + WebApplicationContext webAppContext = initWebApplicationContext(); + Assert.state(webAppContext != null, "WebApplicationContext not provided by concrete MockMvcBuilder"); + + ServletContext servletContext = webAppContext.getServletContext(); + Assert.state(servletContext != null,"ServletContext not configured by concrete MockMvcBuilder"); + + Filter[] filterArray = this.filters.toArray(new Filter[this.filters.size()]); + MockServletConfig mockServletConfig = new MockServletConfig(servletContext); + + return super.createMockMvc(filterArray, mockServletConfig, webAppContext, + this.defaultRequestBuilder, this.globalResultMatchers, this.globalResultHandlers); + } + + /** + * Return the WebApplicationContext to use. The return value must not be + * {@code null}. Further, the {@code WebApplicationContext} should be + * configured with a {@code ServletContext}. + */ + protected abstract WebApplicationContext initWebApplicationContext(); + +} diff --git a/src/main/java/org/springframework/test/web/server/setup/ConfigurableContextMockMvcBuilder.java b/src/main/java/org/springframework/test/web/server/setup/ConfigurableContextMockMvcBuilder.java deleted file mode 100644 index 8643bc0..0000000 --- a/src/main/java/org/springframework/test/web/server/setup/ConfigurableContextMockMvcBuilder.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2002-2011 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.server.setup; - -import javax.servlet.RequestDispatcher; - -import org.springframework.context.ApplicationContextInitializer; -import org.springframework.core.io.DefaultResourceLoader; -import org.springframework.core.io.FileSystemResourceLoader; -import org.springframework.core.io.ResourceLoader; -import org.springframework.mock.web.MockRequestDispatcher; -import org.springframework.mock.web.MockServletContext; -import org.springframework.web.context.ConfigurableWebApplicationContext; -import org.springframework.web.context.WebApplicationContext; - -/** - * A {@link ContextMockMvcBuilder} variant that expects a Spring {@link ConfigurableWebApplicationContext} and - * provides methods to further initialize the context by setting active profiles, applying - * {@link ApplicationContextInitializer}s to it and so on. - * - */ -public class ConfigurableContextMockMvcBuilder extends ContextMockMvcBuilder { - - private final ConfigurableWebApplicationContext applicationContext; - - private String webResourceBasePath = ""; - - private ResourceLoader webResourceLoader = new FileSystemResourceLoader(); - - protected ConfigurableContextMockMvcBuilder(ConfigurableWebApplicationContext applicationContext) { - super(applicationContext); - this.applicationContext = applicationContext; - } - - /** - * Specify the location of web application root directory. - * - * If {@code isClasspathRelative} is {@code false} the directory path may be relative to the JVM working - * directory (e.g. "src/main/webapp") or fully qualified (e.g. "file:///home/user/webapp"). Or otherwise it - * should be relative to the classpath (e.g. "org/examples/myapp/config"). - * - * @param warRootDir the Web application root directory (should not end with a slash) - */ - public ConfigurableContextMockMvcBuilder configureWarRootDir(String warRootDir, boolean isClasspathRelative) { - this.webResourceBasePath = warRootDir; - this.webResourceLoader = isClasspathRelative ? new DefaultResourceLoader() : new FileSystemResourceLoader(); - return this; - } - - public ConfigurableContextMockMvcBuilder activateProfiles(String...profiles) { - applicationContext.getEnvironment().setActiveProfiles(profiles); - return this; - } - - @SuppressWarnings("unchecked") - public
- ConfigurableContextMockMvcBuilder applyInitializers(ApplicationContextInitializer ... initializers) { - - for (ApplicationContextInitializer initializer : initializers) { - initializer.initialize((T) applicationContext); - } - return this; - } - - @Override - protected WebApplicationContext initApplicationContext() { - - MockServletContext servletContext = new MockServletContext(webResourceBasePath, webResourceLoader) { - // For DefaultServletHttpRequestHandler .. - public RequestDispatcher getNamedDispatcher(String path) { - return (path.equals("default")) ? - new MockRequestDispatcher(path) : super.getNamedDispatcher(path); - } - }; - - applicationContext.setServletContext(servletContext); - applicationContext.refresh(); - - return applicationContext; - } - -} diff --git a/src/main/java/org/springframework/test/web/server/setup/ContextMockMvcBuilder.java b/src/main/java/org/springframework/test/web/server/setup/ContextMockMvcBuilder.java index c0a2a1a..c3453b1 100644 --- a/src/main/java/org/springframework/test/web/server/setup/ContextMockMvcBuilder.java +++ b/src/main/java/org/springframework/test/web/server/setup/ContextMockMvcBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2011-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. @@ -16,127 +16,113 @@ package org.springframework.test.web.server.setup; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import org.springframework.beans.BeanUtils; -import org.springframework.beans.factory.BeanFactoryUtils; -import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.core.OrderComparator; -import org.springframework.test.web.server.AbstractMockMvcBuilder; -import org.springframework.test.web.server.MockMvc; -import org.springframework.util.Assert; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.FileSystemResourceLoader; +import org.springframework.core.io.ResourceLoader; +import org.springframework.mock.web.MockRequestDispatcher; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.ConfigurableWebApplicationContext; import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.servlet.DispatcherServlet; -import org.springframework.web.servlet.HandlerAdapter; -import org.springframework.web.servlet.HandlerExceptionResolver; -import org.springframework.web.servlet.HandlerMapping; -import org.springframework.web.servlet.LocaleResolver; -import org.springframework.web.servlet.RequestToViewNameTranslator; -import org.springframework.web.servlet.ViewResolver; -import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; -import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; -import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; -import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter; -import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter; -import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver; -import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping; -import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver; -import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; -import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator; -import org.springframework.web.servlet.view.InternalResourceViewResolver; /** - * Builds a {@link MockMvc} by detecting Spring MVC infrastructure components in a Spring - * {@link WebApplicationContext}. - * + * A MockMvcBuilder that discovers controllers and Spring MVC infrastructure + * components in a WebApplicationContext. + * + * Unlike {@link InitializedContextMockMvcBuilder}, which expects a fully + * initialized WebApplicationContext, this MockMvcBuilder provides methods to + * initialize various aspects of the WebApplicationContext such activating + * profiles, configuring the root of the webapp directory (classpath or file + * system-relative), and others. + * + * @author Rossen Stoyanchev */ -public class ContextMockMvcBuilder extends AbstractMockMvcBuilder { +public class ContextMockMvcBuilder extends AbstractMockMvcBuilder
{ - private final WebApplicationContext applicationContext; + private final ConfigurableWebApplicationContext webAppContext; - public ContextMockMvcBuilder(WebApplicationContext applicationContext) { - Assert.notNull(applicationContext, "ApplicationContext is required"); - this.applicationContext = applicationContext; - } + private String webResourceBasePath = ""; - @Override - protected WebApplicationContext initApplicationContext() { - return applicationContext; - } - - @Override - protected List initHandlerMappings() { - List result = getOrderedBeans(HandlerMapping.class); - if (result.isEmpty()) { - result.add(new BeanNameUrlHandlerMapping()); - result.add(new DefaultAnnotationHandlerMapping()); - } - return result; - } - - @Override - protected List initHandlerAdapters() { - List result = getOrderedBeans(HandlerAdapter.class); - if (result.isEmpty()) { - result.add(new HttpRequestHandlerAdapter()); - result.add(new SimpleControllerHandlerAdapter()); - result.add(new AnnotationMethodHandlerAdapter()); - } - return result; + private ResourceLoader webResourceLoader = new FileSystemResourceLoader(); + + + /** + * Protected constructor. Not intended for direct instantiation. + * @see MockMvcBuilders#annotationConfigSetup(Class...) + * @see MockMvcBuilders#xmlConfigSetup(String...) + */ + public ContextMockMvcBuilder(ConfigurableWebApplicationContext applicationContext) { + this.webAppContext = applicationContext; } - - @Override - protected List initHandlerExceptionResolvers() { - List result = getOrderedBeans(HandlerExceptionResolver.class); - if (result.isEmpty()) { - result.add(new AnnotationMethodHandlerExceptionResolver()); - result.add(new ResponseStatusExceptionResolver()); - result.add(new DefaultHandlerExceptionResolver()); - } - return result; + + /** + * Specify the location of the web application root directory. + * If {@code isClasspathRelative} is "false" the directory is interpreted either as being + * relative to the JVM working directory (e.g. "src/main/webapp") or as a fully qualified + * file system path (e.g. "file:///home/user/webapp"). + *
Otherwise if {@code isClasspathRelative} is "true" the directory should be relative + * to the classpath (e.g. "org/examples/myapp/config"). + * + * @param warRootDir the Web application root directory (should not end with a slash) + */ + public ContextMockMvcBuilder configureWebAppRootDir(String warRootDir, boolean isClasspathRelative) { + this.webResourceBasePath = warRootDir; + this.webResourceLoader = isClasspathRelative ? new DefaultResourceLoader() : new FileSystemResourceLoader(); + return this; } - @Override - protected List
initViewResolvers() { - List result = getOrderedBeans(ViewResolver.class); - if (result.isEmpty()) { - result.add(new InternalResourceViewResolver()); - } - return result; + /** + * Activate the given profiles before the application context is "refreshed". + */ + public ContextMockMvcBuilder activateProfiles(String...profiles) { + this.webAppContext.getEnvironment().setActiveProfiles(profiles); + return this; } - private List getOrderedBeans(Class beanType) { - List components = new ArrayList (); - Map beans = - BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, beanType, true, false); - if (!beans.isEmpty()) { - components.addAll(beans.values()); - OrderComparator.sort(components); + /** + * Apply the given {@link ApplicationContextInitializer}s before the application context is "refreshed". + */ + @SuppressWarnings("unchecked") + public + ContextMockMvcBuilder applyInitializers(ApplicationContextInitializer ... initializers) { + + for (ApplicationContextInitializer initializer : initializers) { + initializer.initialize((T) this.webAppContext); } - return components; + return this; } - @Override - protected RequestToViewNameTranslator initViewNameTranslator() { - String name = DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME; - return getBeanByName(name, RequestToViewNameTranslator.class, DefaultRequestToViewNameTranslator.class); - } @Override - protected LocaleResolver initLocaleResolver() { - String name = DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME; - return getBeanByName(name, LocaleResolver.class, AcceptHeaderLocaleResolver.class); + protected WebApplicationContext initWebApplicationContext() { + + ServletContext servletContext = new MockServletContext(this.webResourceBasePath, this.webResourceLoader) { + // Required for DefaultServletHttpRequestHandler... + public RequestDispatcher getNamedDispatcher(String path) { + return (path.equals("default")) ? new MockRequestDispatcher(path) : super.getNamedDispatcher(path); + } + }; + + this.webAppContext.setServletContext(servletContext); + this.webAppContext.refresh(); + + return this.webAppContext; } - private T getBeanByName(String name, Class requiredType, Class extends T> defaultType) { - try { - return applicationContext.getBean(name, requiredType); - } - catch (NoSuchBeanDefinitionException ex) { - return (defaultType != null) ? BeanUtils.instantiate(defaultType) : null; - } + /** + * Set a parent context before the application context is "refreshed". + * + * The parent context is expected to have be fully initialized. + * + *
Caution: this method is potentially subject to change depending + * on the outcome of SPR-5243 and SPR-5613. + */ + public ContextMockMvcBuilder setParentContext(ApplicationContext parentContext) { + this.webAppContext.setParent(parentContext); + return this; } - } diff --git a/src/main/java/org/springframework/test/web/server/setup/InitializedContextMockMvcBuilder.java b/src/main/java/org/springframework/test/web/server/setup/InitializedContextMockMvcBuilder.java new file mode 100644 index 0000000..7c28e46 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/setup/InitializedContextMockMvcBuilder.java @@ -0,0 +1,49 @@ +/* + * Copyright 2011-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.server.setup; + +import org.springframework.util.Assert; +import org.springframework.web.context.WebApplicationContext; + +/** + * A MockMvcBuilder that discovers controllers and Spring MVC infrastructure + * components in a WebApplicationContext. + * + * TODO: merge this into AbstractMockMvcBuilder in 3.2 becoming DefaultMockMvcBuilder + * + * @author Rossen Stoyanchev + */ +public class InitializedContextMockMvcBuilder extends AbstractMockMvcBuilder
{ + + private final WebApplicationContext webAppContext; + + /** + * Protected constructor. Not intended for direct instantiation. + * @see MockMvcBuilders#webApplicationContextSetup(WebApplicationContext) + */ + protected InitializedContextMockMvcBuilder(WebApplicationContext wac) { + Assert.notNull(wac, "WebApplicationContext is required"); + Assert.notNull(wac.getServletContext(), "WebApplicationContext must have a ServletContext"); + this.webAppContext = wac; + } + + @Override + protected WebApplicationContext initWebApplicationContext() { + return this.webAppContext; + } + +} diff --git a/src/main/java/org/springframework/test/web/server/setup/MockMvcBuilders.java b/src/main/java/org/springframework/test/web/server/setup/MockMvcBuilders.java index 9f5431d..fcbb66e 100644 --- a/src/main/java/org/springframework/test/web/server/setup/MockMvcBuilders.java +++ b/src/main/java/org/springframework/test/web/server/setup/MockMvcBuilders.java @@ -1,60 +1,86 @@ +/* + * Copyright 2011 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.server.setup; +import javax.servlet.ServletContext; + import org.springframework.context.annotation.Configuration; +import org.springframework.stereotype.Controller; import org.springframework.test.web.server.MockMvc; +import org.springframework.test.web.server.MockMvcBuilder; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.context.support.XmlWebApplicationContext; +/** + * The main class to import to access all available {@link MockMvcBuilder}s. + * + * Eclipse users: consider adding this class as a Java editor + * favorite. To navigate, open the Preferences and type "favorites". + * + * @author Rossen Stoyanchev + */ public class MockMvcBuilders { /** - * Build a {@link MockMvc} from a set of controllers with @{@link RequestMapping} methods. - * - * @param controllers controllers with @{@link RequestMapping} methods to include in the setup - */ - public static StandaloneMockMvcBuilder standaloneMvcSetup(Object...controllers) { - return new StandaloneMockMvcBuilder(controllers); - } - - /** - * Create a {@link ConfigurableContextMockMvcBuilder} from Spring Java-based configuration. - * - * @param configurationClasses @{@link Configuration} classes to use to create a WebApplicationContext + * Build a {@link MockMvc} from Java-based Spring configuration. + * @param configClasses one or more @{@link Configuration} classes */ - public static ConfigurableContextMockMvcBuilder annotationConfigMvcSetup(Class>...configurationClasses) { - Assert.notEmpty(configurationClasses, "At least one @Configuration class is required"); + public static ContextMockMvcBuilder annotationConfigSetup(Class>... configClasses) { + Assert.notEmpty(configClasses, "At least one @Configuration class is required"); AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); - context.register(configurationClasses); - return new ConfigurableContextMockMvcBuilder(context); + context.register(configClasses); + return new ContextMockMvcBuilder(context); } - + /** - * Create a {@link ConfigurableContextMockMvcBuilder} from Spring XML configuration. - * - * @param configLocations XML configuration file locations
For example: - *- *
- {@code classpath:org/example/config/*-context.xml} - *
- {@code file:src/main/webapp/WEB-INF/config/*-context.xml} + * Build a {@link MockMvc} from XML-based Spring configuration. + * @param configLocations XML configuration file locations: + *
+ *
*/ - public static ConfigurableContextMockMvcBuilder xmlConfigMvcSetup(String...configLocations) { + public static ContextMockMvcBuilder xmlConfigSetup(String... configLocations) { Assert.notEmpty(configLocations, "At least one XML config location is required"); XmlWebApplicationContext context = new XmlWebApplicationContext(); context.setConfigLocations(configLocations); - return new ConfigurableContextMockMvcBuilder(context); + return new ContextMockMvcBuilder(context); + } + + /** + * Build a {@link MockMvc} from a fully initialized {@link WebApplicationContext} + * The context must have been setup with a {@link ServletContext} and refreshed. + */ + public static InitializedContextMockMvcBuilder webApplicationContextSetup(WebApplicationContext context) { + return new InitializedContextMockMvcBuilder(context); } /** - * Bulid a {@link MockMvc} from a fully initialized {@link WebApplicationContext}. - * This may be useful if you already have a context initialized through the Spring TestContext framework. + * Build a {@link MockMvc} by providing @{@link Controller} instances and configuring + * directly the required Spring MVC components rather than having them looked up in + * a Spring ApplicationContext. + * @param controllers one or more controllers with @{@link RequestMapping} methods */ - public static ContextMockMvcBuilder applicationContextMvcSetup(WebApplicationContext applicationContext) { - Assert.notNull(applicationContext, "WebApplicationContext is required"); - Assert.notNull(applicationContext.getServletContext(), "WebApplicationContext must have a ServletContext"); - return new ContextMockMvcBuilder(applicationContext); + public static StandaloneMockMvcBuilder standaloneSetup(Object... controllers) { + return new StandaloneMockMvcBuilder(controllers); } } diff --git a/src/main/java/org/springframework/test/web/server/setup/MockWebApplicationContext.java b/src/main/java/org/springframework/test/web/server/setup/MockWebApplicationContext.java new file mode 100644 index 0000000..b5babda --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/setup/MockWebApplicationContext.java @@ -0,0 +1,341 @@ +/* + * Copyright 2011-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.server.setup; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletContext; + +import org.springframework.beans.BeansException; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.support.StaticListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceResolvable; +import org.springframework.context.NoSuchMessageException; +import org.springframework.context.support.DelegatingMessageSource; +import org.springframework.core.env.Environment; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.ObjectUtils; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.ServletContextResourcePatternResolver; + +/** + * A mock WebApplicationContext that accepts registrations of object instances. + * + *- {@code classpath:org/example/config/*-context.xml} + *
- {@code file:src/main/webapp/WEB-INF/config/*-context.xml} + *
- etc. *
As registered object instances are instantiated and initialized + * externally, there is no wiring, bean initialization, lifecycle events, as + * well as no pre-processing and post-processing hooks typically associated with + * beans managed by an {@link ApplicationContext}. Just a simple lookup into a + * {@link StaticListableBeanFactory}. + * + * @author Rossen Stoyanchev + */ +class MockWebApplicationContext implements WebApplicationContext { + + private final ServletContext servletContext; + + private final StubBeanFactory beanFactory = new StubBeanFactory(); + + private final String id = ObjectUtils.identityToString(this); + + private final String displayName = ObjectUtils.identityToString(this); + + private final long startupDate = System.currentTimeMillis(); + + private final Environment environment = new StandardEnvironment(); + + private final MessageSource messageSource = new DelegatingMessageSource(); + + private final ResourcePatternResolver resourcePatternResolver; + + + /** + * Class constructor. + */ + public MockWebApplicationContext(ServletContext servletContext) { + this.servletContext = servletContext; + this.resourcePatternResolver = new ServletContextResourcePatternResolver(servletContext); + } + + /** + * Returns an instance that can initialize {@link ApplicationContextAware} beans. + */ + public AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException { + return this.beanFactory; + } + + public ServletContext getServletContext() { + return this.servletContext; + } + + //--------------------------------------------------------------------- + // Implementation of ApplicationContext interface + //--------------------------------------------------------------------- + + public String getId() { + return this.id; + } + + public String getApplicationName() { + return ""; + } + + public String getDisplayName() { + return this.displayName; + } + + public long getStartupDate() { + return this.startupDate; + } + + public ApplicationContext getParent() { + return null; + } + + public Environment getEnvironment() { + return this.environment ; + } + + public void addBean(String name, Object bean) { + this.beanFactory.addBean(name, bean); + } + + public void addBeans(List> beans) { + for (Object bean : beans) { + String name = bean.getClass().getName() + "#" + ObjectUtils.getIdentityHexString(bean); + this.beanFactory.addBean(name, bean); + } + } + + //--------------------------------------------------------------------- + // Implementation of BeanFactory interface + //--------------------------------------------------------------------- + + public Object getBean(String name) throws BeansException { + return this.beanFactory.getBean(name); + } + + public
T getBean(String name, Class requiredType) throws BeansException { + return this.beanFactory.getBean(name, requiredType); + } + + public T getBean(Class requiredType) throws BeansException { + return this.beanFactory.getBean(requiredType); + } + + public Object getBean(String name, Object... args) throws BeansException { + return this.beanFactory.getBean(name, args); + } + + public boolean containsBean(String name) { + return this.beanFactory.containsBean(name); + } + + public boolean isSingleton(String name) throws NoSuchBeanDefinitionException { + return this.beanFactory.isSingleton(name); + } + + public boolean isPrototype(String name) throws NoSuchBeanDefinitionException { + return this.beanFactory.isPrototype(name); + } + + public boolean isTypeMatch(String name, Class> targetType) throws NoSuchBeanDefinitionException { + return this.beanFactory.isTypeMatch(name, targetType); + } + + public Class> getType(String name) throws NoSuchBeanDefinitionException { + return this.beanFactory.getType(name); + } + + public String[] getAliases(String name) { + return this.beanFactory.getAliases(name); + } + + //--------------------------------------------------------------------- + // Implementation of ListableBeanFactory interface + //--------------------------------------------------------------------- + + public boolean containsBeanDefinition(String beanName) { + return this.beanFactory.containsBeanDefinition(beanName); + } + + public int getBeanDefinitionCount() { + return this.beanFactory.getBeanDefinitionCount(); + } + + public String[] getBeanDefinitionNames() { + return this.beanFactory.getBeanDefinitionNames(); + } + + public String[] getBeanNamesForType(Class> type) { + return this.beanFactory.getBeanNamesForType(type); + } + + public String[] getBeanNamesForType(Class> type, boolean includeNonSingletons, boolean allowEagerInit) { + return this.beanFactory.getBeanNamesForType(type, includeNonSingletons, allowEagerInit); + } + + public Map getBeansOfType(Class type) throws BeansException { + return this.beanFactory.getBeansOfType(type); + } + + public Map getBeansOfType(Class type, boolean includeNonSingletons, boolean allowEagerInit) + throws BeansException { + + return this.beanFactory.getBeansOfType(type, includeNonSingletons, allowEagerInit); + } + + public Map getBeansWithAnnotation(Class extends Annotation> annotationType) + throws BeansException { + + return this.beanFactory.getBeansWithAnnotation(annotationType); + } + + public A findAnnotationOnBean(String beanName, Class annotationType) { + return this.beanFactory.findAnnotationOnBean(beanName, annotationType); + } + + //--------------------------------------------------------------------- + // Implementation of HierarchicalBeanFactory interface + //--------------------------------------------------------------------- + + public BeanFactory getParentBeanFactory() { + return null; + } + + public boolean containsLocalBean(String name) { + return this.beanFactory.containsBean(name); + } + + //--------------------------------------------------------------------- + // Implementation of MessageSource interface + //--------------------------------------------------------------------- + + public String getMessage(String code, Object args[], String defaultMessage, Locale locale) { + return this.messageSource.getMessage(code, args, defaultMessage, locale); + } + + public String getMessage(String code, Object args[], Locale locale) throws NoSuchMessageException { + return this.messageSource.getMessage(code, args, locale); + } + + public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException { + return this.messageSource.getMessage(resolvable, locale); + } + + //--------------------------------------------------------------------- + // Implementation of ResourceLoader interface + //--------------------------------------------------------------------- + + public ClassLoader getClassLoader() { + return null; + } + + public Resource getResource(String location) { + return this.resourcePatternResolver.getResource(location); + } + + //--------------------------------------------------------------------- + // Other + //--------------------------------------------------------------------- + + public void publishEvent(ApplicationEvent event) { + } + + public Resource[] getResources(String locationPattern) throws IOException { + return this.resourcePatternResolver.getResources(locationPattern); + } + + + /** + * An extension of StaticListableBeanFactory that implements + * AutowireCapableBeanFactory in order to allow bean initialization of + * {@link ApplicationContextAware} singletons. + */ + private class StubBeanFactory extends StaticListableBeanFactory implements AutowireCapableBeanFactory { + + public Object initializeBean(Object existingBean, String beanName) throws BeansException { + if (existingBean instanceof ApplicationContextAware) { + ((ApplicationContextAware) existingBean).setApplicationContext(MockWebApplicationContext.this); + } + return existingBean; + } + + public T createBean(Class beanClass) throws BeansException { + throw new UnsupportedOperationException("Bean creation is not supported"); + } + + @SuppressWarnings("rawtypes") + public Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException { + throw new UnsupportedOperationException("Bean creation is not supported"); + } + + @SuppressWarnings("rawtypes") + public Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException { + return null; + } + + public void autowireBean(Object existingBean) throws BeansException { + throw new UnsupportedOperationException("Autowiring is not supported"); + } + + public void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck) throws BeansException { + throw new UnsupportedOperationException("Autowiring is not supported"); + } + + public Object configureBean(Object existingBean, String beanName) throws BeansException { + throw new UnsupportedOperationException("Configuring a bean is not supported"); + } + + public Object resolveDependency(DependencyDescriptor descriptor, String beanName) throws BeansException { + throw new UnsupportedOperationException("Dependency resolution is not supported"); + } + + public Object resolveDependency(DependencyDescriptor descriptor, String beanName, Set autowiredBeanNames, + TypeConverter typeConverter) throws BeansException { + throw new UnsupportedOperationException("Dependency resolution is not supported"); + } + + public void applyBeanPropertyValues(Object existingBean, String beanName) throws BeansException { + throw new UnsupportedOperationException("Bean property initialization is not supported"); + } + + public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) + throws BeansException { + throw new UnsupportedOperationException("Post processing is not supported"); + } + + public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) + throws BeansException { + throw new UnsupportedOperationException("Post processing is not supported"); + } + } +} diff --git a/src/main/java/org/springframework/test/web/server/setup/PatternMappingFilterProxy.java b/src/main/java/org/springframework/test/web/server/setup/PatternMappingFilterProxy.java new file mode 100644 index 0000000..5ca0aa6 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/setup/PatternMappingFilterProxy.java @@ -0,0 +1,129 @@ +/* + * Copyright 2002-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.server.setup; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.util.Assert; +import org.springframework.web.util.UrlPathHelper; + +/** + * A Filter that invokes a delegate {@link Filter} only if the request URL + * matches the pattern it is mapped to using pattern matching as defined in the + * Servlet spec. + * + * @author Rob Winch + */ +final class PatternMappingFilterProxy implements Filter { + + private static final String EXTENSION_MAPPING_PATTERN = "*."; + + private static final String PATH_MAPPING_PATTERN = "/*"; + + private static final UrlPathHelper urlPathHelper = new UrlPathHelper(); + + private final Filter delegate; + + /** Patterns that require an exact match, e.g. "/test" */ + private final List exactMatches = new ArrayList (); + + /** Patterns that require the URL to have a specific prefix, e.g. "/test/*" */ + private final List startsWithMatches = new ArrayList (); + + /** Patterns that require the request URL to have a specific suffix, e.g. "*.html" */ + private final List endsWithMatches = new ArrayList (); + + + /** + * Creates a new instance. + */ + public PatternMappingFilterProxy(Filter delegate, String... urlPatterns) { + Assert.notNull(delegate, "A delegate Filter is required"); + this.delegate = delegate; + for(String urlPattern : urlPatterns) { + addUrlPattern(urlPattern); + } + } + + private void addUrlPattern(String urlPattern) { + Assert.notNull(urlPattern, "Found null URL Pattern"); + if(urlPattern.startsWith(EXTENSION_MAPPING_PATTERN)) { + this.endsWithMatches.add(urlPattern.substring(1, urlPattern.length())); + } else if(urlPattern.equals(PATH_MAPPING_PATTERN)) { + this.startsWithMatches.add(""); + } + else if (urlPattern.endsWith(PATH_MAPPING_PATTERN)) { + this.startsWithMatches.add(urlPattern.substring(0, urlPattern.length() - 1)); + this.exactMatches.add(urlPattern.substring(0, urlPattern.length() - 2)); + } else { + if("".equals(urlPattern)) { + urlPattern = "/"; + } + this.exactMatches.add(urlPattern); + } + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + String requestPath = urlPathHelper.getPathWithinApplication(httpRequest); + + if(matches(requestPath)) { + this.delegate.doFilter(request, response, filterChain); + } else { + filterChain.doFilter(request, response); + } + } + + private boolean matches(String requestPath) { + for(String pattern : this.exactMatches) { + if(pattern.equals(requestPath)) { + return true; + } + } + if(!requestPath.startsWith("/")) { + return false; + } + for(String pattern : this.endsWithMatches) { + if(requestPath.endsWith(pattern)) { + return true; + } + } + for(String pattern : this.startsWithMatches) { + if(requestPath.startsWith(pattern)) { + return true; + } + } + return false; + } + + public void init(FilterConfig filterConfig) throws ServletException { + this.delegate.init(filterConfig); + } + + public void destroy() { + this.delegate.destroy(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/test/web/server/setup/StandaloneMockMvcBuilder.java b/src/main/java/org/springframework/test/web/server/setup/StandaloneMockMvcBuilder.java index 2eb0ecd..8a5c6da 100644 --- a/src/main/java/org/springframework/test/web/server/setup/StandaloneMockMvcBuilder.java +++ b/src/main/java/org/springframework/test/web/server/setup/StandaloneMockMvcBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2011 the original author or authors. + * Copyright 2011-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. @@ -21,205 +21,380 @@ import java.util.Collections; import java.util.List; import java.util.Locale; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletContext; -import org.springframework.context.ApplicationContextAware; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; import org.springframework.format.support.DefaultFormattingConversionService; import org.springframework.format.support.FormattingConversionService; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.mock.web.MockServletContext; -import org.springframework.test.web.server.AbstractMockMvcBuilder; -import org.springframework.test.web.server.MockMvc; import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; import org.springframework.util.ObjectUtils; import org.springframework.validation.Validator; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.context.WebApplicationContext; -import org.springframework.web.context.support.GenericWebApplicationContext; -import org.springframework.web.servlet.HandlerAdapter; +import org.springframework.web.context.support.WebApplicationObjectSupport; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.FlashMapManager; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.LocaleResolver; -import org.springframework.web.servlet.RequestToViewNameTranslator; import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewResolver; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.InterceptorRegistration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import org.springframework.web.servlet.handler.MappedInterceptor; import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; -import org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver; -import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; -import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; -import org.springframework.web.servlet.view.BeanNameViewResolver; +import org.springframework.web.servlet.theme.FixedThemeResolver; import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator; +import org.springframework.web.servlet.view.InternalResourceViewResolver; /** - * Builds a {@link MockMvc} by instantiating the required Spring MVC components directly rather than detecting - * them in a Spring ApplicationContext. This makes it possible to build more "lightweight" and very focused tests - * involving one or just a few controllers. - * - * The resulting setup is geared at supporting controllers with @{@link RequestMapping} methods. View resolution - * can be configured by providing a list of {@link ViewResolver}s. When view resolution is left not configured, a - * fixed, no-op {@link View} is used effectively ignoring rendering. - * - */ -public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder { - + * A MockMvcBuilder that can be configured with controller instances allowing + * full control over the instantiation and the initialization of controllers and + * their dependencies similar to plain unit tests, and also making it possible + * to test one controller at a time. + * + *
+ * This builder creates the minimum infrastructure required by the + * {@link DispatcherServlet} to serve requests with annotated controllers and + * also provides various methods to customize it. The resulting configuration + * and customizations possible are equivalent to using the {@link EnableWebMvc + * MVC Java config} except with builder style methods rather than the callback. + * + *
+ * To configure view resolution, either select a "fixed" view to use for every + * performed request (see {@link #setSingleView(View)}) or provide a list of + * {@code ViewResolver}'s, see {@link #setViewResolvers(ViewResolver...)}. + * + * @author Rossen Stoyanchev + */ +public class StandaloneMockMvcBuilder extends AbstractMockMvcBuilder
{ + private final Object[] controllers; - - private List > messageConverters; - - private Validator validator; - - private FormattingConversionService conversionService = new DefaultFormattingConversionService(); - + + private List > messageConverters = new ArrayList >(); + + private List customArgumentResolvers = new ArrayList (); + + private List customReturnValueHandlers = new ArrayList (); + private final List mappedInterceptors = new ArrayList (); - private List extends ViewResolver> viewResolvers; + private Validator validator = null; + + private FormattingConversionService conversionService = null; + + private List handlerExceptionResolvers = new ArrayList (); + + private List viewResolvers; + + private LocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); + + private FlashMapManager flashMapManager = null; + + private boolean useSuffixPatternMatch = true; - private GenericWebApplicationContext applicationContext; + private boolean useTrailingSlashPatternMatch = true; - protected StandaloneMockMvcBuilder(Object[] controllers) { + + /** + * Protected constructor. Not intended for direct instantiation. + * @see MockMvcBuilders#standaloneSetup(Object...) + */ + protected StandaloneMockMvcBuilder(Object... controllers) { Assert.isTrue(!ObjectUtils.isEmpty(controllers), "At least one controller is required"); this.controllers = controllers; - this.applicationContext = new GenericWebApplicationContext(new MockServletContext()); - this.applicationContext.refresh(); } + /** + * Set the message converters to use in argument resolvers and in return value + * handlers, which support reading and/or writing to the body of the request + * and response. If no message converters are added to the list, a default + * list of converters is added instead. + */ public StandaloneMockMvcBuilder setMessageConverters(HttpMessageConverter>...messageConverters) { this.messageConverters = Arrays.asList(messageConverters); return this; } + /** + * Provide a custom {@link Validator} instead of the one created by default. + * The default implementation used, assuming JSR-303 is on the classpath, is + * {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean}. + */ public StandaloneMockMvcBuilder setValidator(Validator validator) { this.validator = validator; - this.applicationContext.getAutowireCapableBeanFactory().initializeBean(validator, "validator"); return this; } + /** + * Provide a conversion service with custom formatters and converters. + * If not set, a {@link DefaultFormattingConversionService} is used by default. + */ public StandaloneMockMvcBuilder setConversionService(FormattingConversionService conversionService) { this.conversionService = conversionService; return this; } - + + /** + * Add interceptors mapped to all incoming requests. + */ public StandaloneMockMvcBuilder addInterceptors(HandlerInterceptor... interceptors) { - mapInterceptors(null, interceptors); + addMappedInterceptors(null, interceptors); return this; } - public StandaloneMockMvcBuilder mapInterceptors(String[] pathPatterns, HandlerInterceptor... interceptors) { + /** + * Add interceptors mapped to a set of path patterns. + */ + public StandaloneMockMvcBuilder addMappedInterceptors(String[] pathPatterns, HandlerInterceptor... interceptors) { for (HandlerInterceptor interceptor : interceptors) { - mappedInterceptors.add(new MappedInterceptor(pathPatterns, interceptor)); + this.mappedInterceptors.add(new MappedInterceptor(pathPatterns, interceptor)); } return this; } - + + /** + * Provide custom resolvers for controller method arguments. + */ + public StandaloneMockMvcBuilder setCustomArgumentResolvers(HandlerMethodArgumentResolver... argumentResolvers) { + this.customArgumentResolvers = Arrays.asList(argumentResolvers); + return this; + } + /** - * Configures a single ViewResolver that always renders using the provided View implementation. - * Provides a simple way to render generated content (e.g. JSON, XML, Atom, etc.) For URL-based view types, - * i.e. sub-classes of AbstractUrlBasedView, use {@link #setViewResolvers(ViewResolver...)} instead. - * - * @param view the default View to return for any view name + * Provide custom handlers for controller method return values. */ - public StandaloneMockMvcBuilder configureFixedViewResolver(View view) { - viewResolvers = Collections.singletonList(new FixedViewResolver(view)); + public StandaloneMockMvcBuilder setCustomReturnValueHandlers(HandlerMethodReturnValueHandler... handlers) { + this.customReturnValueHandlers = Arrays.asList(handlers); return this; } + /** - * Configures view resolution with the given {@link ViewResolver}s. - * By default, if no ViewResolvers have been configured, a View that doesn't do anything is used. - * - * Most ViewResolver types should work as expected. This excludes {@link BeanNameViewResolver} - * since there is no ApplicationContext. - * + * Set the HandlerExceptionResolver types to use. + */ + public void setHandlerExceptionResolvers(List
exceptionResolvers) { + this.handlerExceptionResolvers = exceptionResolvers; + } + + /** + * Set up view resolution with the given {@link ViewResolver}s. + * If not set, an {@link InternalResourceViewResolver} is used by default. */ public StandaloneMockMvcBuilder setViewResolvers(ViewResolver...resolvers) { - viewResolvers = Arrays.asList(resolvers); + this.viewResolvers = Arrays.asList(resolvers); return this; } - - @Override - protected WebApplicationContext initApplicationContext() { - return applicationContext; + + /** + * Sets up a single {@link ViewResolver} that always returns the provided + * view instance. This is a convenient shortcut if you need to use one + * View instance only -- e.g. rendering generated content (JSON, XML, Atom). + */ + public StandaloneMockMvcBuilder setSingleView(View view) { + this.viewResolvers = Collections. singletonList(new StaticViewResolver(view)); + return this; } - @Override - protected List extends HandlerMapping> initHandlerMappings() { - StaticRequestMappingHandlerMapping mapping = new StaticRequestMappingHandlerMapping(); - mapping.registerHandlers(controllers); - mapping.setInterceptors(mappedInterceptors.toArray()); - return Collections.singletonList(mapping); + /** + * Provide a LocaleResolver instance. + * If not provided, the default one used is {@link AcceptHeaderLocaleResolver}. + */ + public StandaloneMockMvcBuilder setLocaleResolver(LocaleResolver localeResolver) { + this.localeResolver = localeResolver; + return this; + } + + /** + * Provide a custom FlashMapManager instance. + * If not provided, {@code SessionFlashMapManager} is used by default. + */ + public StandaloneMockMvcBuilder setFlashMapManager(FlashMapManager flashMapManager) { + this.flashMapManager = flashMapManager; + return this; + } + + /** + * Whether to use suffix pattern match (".*") when matching patterns to + * requests. If enabled a method mapped to "/users" also matches to "/users.*". + * The default value is {@code true}. + */ + public StandaloneMockMvcBuilder setUseSuffixPatternMatch(boolean useSuffixPatternMatch) { + this.useSuffixPatternMatch = useSuffixPatternMatch; + return this; + } + + /** + * Whether to match to URLs irrespective of the presence of a trailing slash. + * If enabled a method mapped to "/users" also matches to "/users/". + *
The default value is {@code true}. + */ + public StandaloneMockMvcBuilder setUseTrailingSlashPatternMatch(boolean useTrailingSlashPatternMatch) { + this.useTrailingSlashPatternMatch = useTrailingSlashPatternMatch; + return this; } @Override - protected List extends HandlerAdapter> initHandlerAdapters() { - RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); - if (messageConverters != null) { - adapter.setMessageConverters(messageConverters); + protected WebApplicationContext initWebApplicationContext() { + ServletContext servletContext = new MockServletContext(); + MockWebApplicationContext cxt = new MockWebApplicationContext(servletContext); + registerMvcSingletons(cxt); + servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, cxt); + return cxt; + } + + private void registerMvcSingletons(MockWebApplicationContext cxt) { + + StandaloneConfiguration configuration = new StandaloneConfiguration(); + + RequestMappingHandlerMapping handlerMapping = configuration.requestMappingHandlerMapping(); + handlerMapping.setServletContext(cxt.getServletContext()); + handlerMapping.setApplicationContext(cxt); + cxt.addBean("requestMappingHandlerMapping", handlerMapping); + + RequestMappingHandlerAdapter handlerAdapter = configuration.requestMappingHandlerAdapter(); + handlerAdapter.setServletContext(cxt.getServletContext()); + handlerAdapter.setApplicationContext(cxt); + handlerAdapter.afterPropertiesSet(); + cxt.addBean("requestMappingHandlerAdapter", handlerAdapter); + + cxt.addBean("handlerExceptionResolver", configuration.handlerExceptionResolver()); + + cxt.addBeans(initViewResolvers(cxt)); + cxt.addBean(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME, this.localeResolver); + cxt.addBean(DispatcherServlet.THEME_RESOLVER_BEAN_NAME, new FixedThemeResolver()); + cxt.addBean(DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, new DefaultRequestToViewNameTranslator()); + + initFlashMapManager(); + cxt.addBean(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, this.flashMapManager); + } + + // TODO: remove in 3.2 + + private void initFlashMapManager() { + if (this.flashMapManager == null) { + String className = "org.springframework.web.servlet.support.DefaultFlashMapManager"; + if (ClassUtils.isPresent(className, getClass().getClassLoader())) { + this.flashMapManager = instantiateClass(className); + } + else { + className = "org.springframework.web.servlet.support.SessionFlashMapManager"; + this.flashMapManager = instantiateClass(className); + } } - - ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer(); - initializer.setConversionService(conversionService); - initializer.setValidator(validator); - adapter.setWebBindingInitializer(initializer); - - adapter.setApplicationContext(applicationContext); // for SpEL expressions in annotations - adapter.afterPropertiesSet(); - - return Collections.singletonList(adapter); } - @Override - protected List extends HandlerExceptionResolver> initHandlerExceptionResolvers() { - ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver(); - if (messageConverters != null) { - exceptionResolver.setMessageConverters( messageConverters); + @SuppressWarnings("unchecked") + private
T instantiateClass(String className) { + Class> clazz; + try { + clazz = ClassUtils.forName(className, StandaloneMockMvcBuilder.class.getClassLoader()); + } + catch (ClassNotFoundException e) { + throw new BeanInitializationException("Could not instantiate " + className, e); } - exceptionResolver.afterPropertiesSet(); - - List resolvers = new ArrayList (); - resolvers.add(exceptionResolver); - resolvers.add(new ResponseStatusExceptionResolver()); - resolvers.add(new DefaultHandlerExceptionResolver()); - - return resolvers; + catch (LinkageError e) { + throw new BeanInitializationException("Could not instantiate " + className, e); + } + return (T) BeanUtils.instantiate(clazz); } - @Override - protected List extends ViewResolver> initViewResolvers() { - viewResolvers = (viewResolvers == null) ? - Arrays.asList(new FixedViewResolver(NOOP_VIEW)) : viewResolvers; - - for (Object vr : viewResolvers) { - if (vr instanceof ApplicationContextAware) { - ((ApplicationContextAware) vr).setApplicationContext(applicationContext); + private List initViewResolvers(WebApplicationContext wac) { + + this.viewResolvers = (this.viewResolvers == null) ? + Arrays. asList(new InternalResourceViewResolver()) : viewResolvers; + + for (Object viewResolver : this.viewResolvers) { + if (viewResolver instanceof WebApplicationObjectSupport) { + ((WebApplicationObjectSupport) viewResolver).setApplicationContext(wac); } } - return viewResolvers; + return this.viewResolvers; } - @Override - protected RequestToViewNameTranslator initViewNameTranslator() { - return new DefaultRequestToViewNameTranslator(); - } - @Override - protected LocaleResolver initLocaleResolver() { - return new AcceptHeaderLocaleResolver(); + /** Using the MVC Java configuration as the starting point for the "standalone" setup */ + private class StandaloneConfiguration extends WebMvcConfigurationSupport { + + @Override + public RequestMappingHandlerMapping requestMappingHandlerMapping() { + + StaticRequestMappingHandlerMapping handlerMapping = new StaticRequestMappingHandlerMapping(); + handlerMapping.registerHandlers(controllers); + + handlerMapping.setUseSuffixPatternMatch(useSuffixPatternMatch); + handlerMapping.setUseTrailingSlashMatch(useTrailingSlashPatternMatch); + handlerMapping.setOrder(0); + handlerMapping.setInterceptors(getInterceptors()); + + return handlerMapping; + } + + @Override + protected void configureMessageConverters(List > converters) { + converters.addAll(messageConverters); + } + + @Override + protected void addArgumentResolvers(List argumentResolvers) { + argumentResolvers.addAll(customArgumentResolvers); + } + + @Override + protected void addReturnValueHandlers(List returnValueHandlers) { + returnValueHandlers.addAll(customReturnValueHandlers); + } + + @Override + protected void addInterceptors(InterceptorRegistry registry) { + for (MappedInterceptor interceptor : mappedInterceptors) { + InterceptorRegistration registration = registry.addInterceptor(interceptor.getInterceptor()); + if (interceptor.getPathPatterns() != null) { + registration.addPathPatterns(interceptor.getPathPatterns()); + } + } + } + + @Override + public FormattingConversionService mvcConversionService() { + return (conversionService != null) ? conversionService : super.mvcConversionService(); + } + + @Override + public Validator mvcValidator() { + Validator mvcValidator = (validator != null) ? validator : super.mvcValidator(); + if (mvcValidator instanceof InitializingBean) { + try { + ((InitializingBean) mvcValidator).afterPropertiesSet(); + } + catch (Exception e) { + throw new BeanInitializationException("Failed to initialize Validator", e); + } + } + return mvcValidator; + } + + @Override + protected void configureHandlerExceptionResolvers(List exceptionResolvers) { + exceptionResolvers.addAll(StandaloneMockMvcBuilder.this.handlerExceptionResolvers); + } } - /** - * Allows registering controller instances. - */ + /** A {@code RequestMappingHandlerMapping} that allows registration of controllers */ private static class StaticRequestMappingHandlerMapping extends RequestMappingHandlerMapping { - + public void registerHandlers(Object...handlers) { for (Object handler : handlers) { super.detectHandlerMethods(handler); @@ -227,34 +402,18 @@ public void registerHandlers(Object...handlers) { } } - /** - * Resolves all view names to the same fixed View. - */ - private static class FixedViewResolver implements ViewResolver { - + /** A {@link ViewResolver} that always returns same View */ + private static class StaticViewResolver implements ViewResolver { + private final View view; - - public FixedViewResolver(View view) { + + public StaticViewResolver(View view) { this.view = view; } public View resolveViewName(String viewName, Locale locale) throws Exception { - return view; + return this.view; } } - - /** - * A View implementation that doesn't do anything. - */ - private static final View NOOP_VIEW = new View() { - - public String getContentType() { - return null; - } - - public void render(Map model, HttpServletRequest request, HttpServletResponse response) - throws Exception { - } - }; } diff --git a/src/main/java/org/springframework/test/web/server/setup/package-info.java b/src/main/java/org/springframework/test/web/server/setup/package-info.java new file mode 100644 index 0000000..2eaa4a2 --- /dev/null +++ b/src/main/java/org/springframework/test/web/server/setup/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright 2011-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. + */ + +/** + * Contains built-in {@code MockMvcBuilder} implementations. + * Use {@link org.springframework.test.web.server.setup.MockMvcBuilders} + * to access to instances of those implementations. + */ +package org.springframework.test.web.server.setup; diff --git a/src/main/java/org/springframework/test/web/support/JsonPathExpectationsHelper.java b/src/main/java/org/springframework/test/web/support/JsonPathExpectationsHelper.java new file mode 100644 index 0000000..2a0a891 --- /dev/null +++ b/src/main/java/org/springframework/test/web/support/JsonPathExpectationsHelper.java @@ -0,0 +1,121 @@ +/* + * Copyright 2011 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.support; + +import static org.springframework.test.web.AssertionErrors.assertTrue; + +import java.text.ParseException; +import java.util.List; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; + +import com.jayway.jsonpath.InvalidPathException; +import com.jayway.jsonpath.JsonPath; + +/** + * A helper class for applying assertions via JSONPath expressions. + * + * @author Rossen Stoyanchev + */ +public class JsonPathExpectationsHelper { + + private final String expression; + + private final JsonPath jsonPath; + + + /** + * Class constructor. + * + * @param expression the JSONPath expression + * @param args arguments to parameterize the JSONPath expression with using the + * formatting specifiers defined in {@link String#format(String, Object...)} + */ + public JsonPathExpectationsHelper(String expression, Object ... args) { + this.expression = String.format(expression, args); + this.jsonPath = JsonPath.compile(this.expression); + } + + /** + * Evaluate the JSONPath and assert the resulting value with the given {@code Matcher}. + */ + @SuppressWarnings("unchecked") + public void assertValue(String content, Matcher matcher) throws ParseException { + T value = (T) evaluateJsonPath(content); + MatcherAssert.assertThat("JSON path: " + this.expression, value, matcher); + } + + private Object evaluateJsonPath(String content) throws ParseException { + String message = "No value for JSON path: " + this.expression + ", exception: "; + try { + return this.jsonPath.read(content); + } + catch (InvalidPathException ex) { + throw new AssertionError(message + ex.getMessage()); + } + catch (ArrayIndexOutOfBoundsException ex) { + throw new AssertionError(message + ex.getMessage()); + } + catch (IndexOutOfBoundsException ex) { + throw new AssertionError(message + ex.getMessage()); + } + } + + /** + * Apply the JSONPath and assert the resulting value. + */ + public void assertValue(Object value) throws ParseException { + assertValue(Matchers.equalTo(value)); + } + + /** + * Evaluate the JSON path and assert the resulting content exists. + */ + public void exists(String content) throws ParseException { + Object value = evaluateJsonPath(content); + String reason = "No value for JSON path: " + this.expression; + assertTrue(reason, value != null); + if (List.class.isInstance(value)) { + assertTrue(reason, !((List>) value).isEmpty()); + } + } + + /** + * Evaluate the JSON path and assert it doesn't point to any content. + */ + public void doesNotExist(String content) throws ParseException { + + Object value; + try { + value = evaluateJsonPath(content); + } + catch (AssertionError ex) { + return; + } + + String reason = String.format("Expected no value for JSON path: %s but found: %s", this.expression, value); + if (List.class.isInstance(value)) { + assertTrue(reason, ((List>) value).isEmpty()); + } + else { + assertTrue(reason, value == null); + } + } + +} diff --git a/src/main/java/org/springframework/test/web/support/XmlExpectationsHelper.java b/src/main/java/org/springframework/test/web/support/XmlExpectationsHelper.java new file mode 100644 index 0000000..aa191f1 --- /dev/null +++ b/src/main/java/org/springframework/test/web/support/XmlExpectationsHelper.java @@ -0,0 +1,100 @@ +/* + * Copyright 2011 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.support; + +import java.io.StringReader; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; + +import org.custommonkey.xmlunit.Diff; +import org.custommonkey.xmlunit.XMLUnit; +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.springframework.test.web.AssertionErrors; +import org.springframework.test.web.server.result.MockMvcResultMatchers; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; + +/** + * A helper class for assertions on XML content. + * + * @author Rossen Stoyanchev + */ +public class XmlExpectationsHelper { + + + /** + * Parse the content as {@link Node} and apply a {@link Matcher}. + * @see org.hamcrest.Matchers#hasXPath + */ + public void assertNode(String content, Matcher super Node> matcher) throws Exception { + Document document = parseXmlString(content); + MatcherAssert.assertThat("Body content", document, matcher); + } + + private Document parseXmlString(String xml) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder documentBuilder = factory.newDocumentBuilder(); + InputSource inputSource = new InputSource(new StringReader(xml)); + Document document = documentBuilder.parse(inputSource); + return document; + } + + /** + * Parse the content as {@link DOMSource} and apply a {@link Matcher}. + * @see xml-matchers + */ + public void assertSource(String content, Matcher super Source> matcher) throws Exception { + Document document = parseXmlString(content); + MatcherAssert.assertThat("Body content", new DOMSource(document), matcher); + } + + /** + * Parse the expected and actual content strings as XML and assert that the + * two are "similar" -- i.e. they contain the same elements and attributes + * regardless of order. + * + * Use of this method assumes the + * XMLUnit library is available. + * + * @param expected the expected XML content + * @param actual the actual XML content + * + * @see MockMvcResultMatchers#xpath(String, Object...) + * @see MockMvcResultMatchers#xpath(String, Map, Object...) + */ + public void assertXmlEqual(String expected, String actual) throws Exception { + + XMLUnit.setIgnoreWhitespace(true); + XMLUnit.setIgnoreComments(true); + XMLUnit.setIgnoreAttributeOrder(true); + + Document control = XMLUnit.buildControlDocument(expected); + Document test = XMLUnit.buildTestDocument(actual); + Diff diff = new Diff(control, test); + if (!diff.similar()) { + AssertionErrors.fail("Body content " + diff.toString()); + } + } + +} diff --git a/src/main/java/org/springframework/test/web/support/XpathExpectationsHelper.java b/src/main/java/org/springframework/test/web/support/XpathExpectationsHelper.java new file mode 100644 index 0000000..1e5e2c1 --- /dev/null +++ b/src/main/java/org/springframework/test/web/support/XpathExpectationsHelper.java @@ -0,0 +1,212 @@ +/* + * Copyright 2011-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.support; + +import static org.springframework.test.web.AssertionErrors.assertEquals; + +import java.io.StringReader; +import java.util.Collections; +import java.util.Map; + +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.util.xml.SimpleNamespaceContext; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +/** + * A helper class for applying assertions via XPath expressions. + * + * @author Rossen Stoyanchev + */ +public class XpathExpectationsHelper { + + private final String expression; + + private final XPathExpression xpathExpression; + + + /** + * Class constructor. + * + * @param expression the XPath expression + * @param namespaces XML namespaces referenced in the XPath expression, or {@code null} + * @param args arguments to parameterize the XPath expression with using the + * formatting specifiers defined in {@link String#format(String, Object...)} + * @throws XPathExpressionException + */ + public XpathExpectationsHelper(String expression, Map
namespaces, Object... args) + throws XPathExpressionException { + + this.expression = String.format(expression, args); + this.xpathExpression = compileXpathExpression(this.expression, namespaces); + } + + private XPathExpression compileXpathExpression(String expression, Map namespaces) + throws XPathExpressionException { + + SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext(); + namespaceContext.setBindings((namespaces != null) ? namespaces : Collections. emptyMap()); + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setNamespaceContext(namespaceContext); + return xpath.compile(expression); + } + + /** + * @return the compiled XPath expression. + */ + protected XPathExpression getXpathExpression() { + return this.xpathExpression; + } + + /** + * Parse the content, evaluate the XPath expression as a {@link Node}, and + * assert it with the given {@code Matcher }. + */ + public void assertNode(String content, final Matcher super Node> matcher) throws Exception { + Document document = parseXmlString(content); + Node node = evaluateXpath(document, XPathConstants.NODE, Node.class); + MatcherAssert.assertThat("Xpath: " + XpathExpectationsHelper.this.expression, node, matcher); + } + + /** + * Parse the given XML content to a {@link Document}. + * + * @param xml the content to parse + * @return the parsed document + * @throws Exception in case of errors + */ + protected Document parseXmlString(String xml) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder documentBuilder = factory.newDocumentBuilder(); + InputSource inputSource = new InputSource(new StringReader(xml)); + Document document = documentBuilder.parse(inputSource); + return document; + } + + /** + * Apply the XPath expression to given document. + * @throws XPathExpressionException + */ + @SuppressWarnings("unchecked") + protected T evaluateXpath(Document document, QName evaluationType, Class expectedClass) + throws XPathExpressionException { + + return (T) getXpathExpression().evaluate(document, evaluationType); + } + + /** + * Apply the XPath expression and assert the resulting content exists. + * @throws Exception if content parsing or expression evaluation fails + */ + public void exists(String content) throws Exception { + assertNode(content, Matchers.notNullValue()); + } + + /** + * Apply the XPath expression and assert the resulting content does not exist. + * @throws Exception if content parsing or expression evaluation fails + */ + public void doesNotExist(String content) throws Exception { + assertNode(content, Matchers.nullValue()); + } + + /** + * Apply the XPath expression and assert the resulting content with the + * given Hamcrest matcher. + * + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertNodeCount(String content, Matcher matcher) throws Exception { + Document document = parseXmlString(content); + NodeList nodeList = evaluateXpath(document, XPathConstants.NODESET, NodeList.class); + String reason = "nodeCount Xpath: " + XpathExpectationsHelper.this.expression; + MatcherAssert.assertThat(reason, nodeList.getLength(), matcher); + } + + /** + * Apply the XPath expression and assert the resulting content as an integer. + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertNodeCount(String content, int expectedCount) throws Exception { + assertNodeCount(content, Matchers.equalTo(expectedCount)); + } + + /** + * Apply the XPath expression and assert the resulting content with the + * given Hamcrest matcher. + * + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertString(String content, Matcher super String> matcher) throws Exception { + Document document = parseXmlString(content); + String result = evaluateXpath(document, XPathConstants.STRING, String.class); + MatcherAssert.assertThat("Xpath: " + XpathExpectationsHelper.this.expression, result, matcher); + } + + /** + * Apply the XPath expression and assert the resulting content as a String. + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertString(String content, String expectedValue) throws Exception { + assertString(content, Matchers.equalTo(expectedValue)); + } + + /** + * Apply the XPath expression and assert the resulting content with the + * given Hamcrest matcher. + * + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertNumber(String content, Matcher super Double> matcher) throws Exception { + Document document = parseXmlString(content); + Double result = evaluateXpath(document, XPathConstants.NUMBER, Double.class); + MatcherAssert.assertThat("Xpath: " + XpathExpectationsHelper.this.expression, result, matcher); + } + + /** + * Apply the XPath expression and assert the resulting content as a Double. + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertNumber(String content, Double expectedValue) throws Exception { + assertNumber(content, Matchers.equalTo(expectedValue)); + } + + /** + * Apply the XPath expression and assert the resulting content as a Boolean. + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertBoolean(String content, boolean expectedValue) throws Exception { + Document document = parseXmlString(content); + String result = evaluateXpath(document, XPathConstants.STRING, String.class); + assertEquals("Xpath:", expectedValue, Boolean.parseBoolean(result)); + } + +} \ No newline at end of file diff --git a/src/main/java/org/springframework/test/web/support/package-info.java b/src/main/java/org/springframework/test/web/support/package-info.java new file mode 100644 index 0000000..43dcbf3 --- /dev/null +++ b/src/main/java/org/springframework/test/web/support/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2011-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. + */ + +/** + * Support classes shared among client and server-side Spring MVC Test classes. + */ +package org.springframework.test.web.support; diff --git a/src/test/java/org/springframework/test/web/Person.java b/src/test/java/org/springframework/test/web/Person.java new file mode 100644 index 0000000..17008b2 --- /dev/null +++ b/src/test/java/org/springframework/test/web/Person.java @@ -0,0 +1,88 @@ +/* + * Copyright 2011 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; + +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlRootElement; + +import org.springframework.util.ObjectUtils; + +@XmlRootElement +public class Person { + + @NotNull + private String name; + + private double someDouble; + + private boolean someBoolean; + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public Person setName(String name) { + this.name = name; + return this; + } + + public double getSomeDouble() { + return someDouble; + } + + public Person setSomeDouble(double someDouble) { + this.someDouble = someDouble; + return this; + } + + public boolean isSomeBoolean() { + return someBoolean; + } + + public Person setSomeBoolean(boolean someBoolean) { + this.someBoolean = someBoolean; + return this; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof Person)) { + return false; + } + Person otherPerson = (Person) other; + return (ObjectUtils.nullSafeEquals(this.name, otherPerson.name) && + ObjectUtils.nullSafeEquals(this.someDouble, otherPerson.someDouble) && + ObjectUtils.nullSafeEquals(this.someBoolean, otherPerson.someBoolean)); + } + + @Override + public String toString() { + return "Person [name=" + this.name + ", someDouble=" + this.someDouble + + ", someBoolean=" + this.someBoolean + "]"; + } + +} \ No newline at end of file diff --git a/src/test/java/org/springframework/test/web/client/MockClientHttpRequestFactoryTests.java b/src/test/java/org/springframework/test/web/client/MockClientHttpRequestFactoryTests.java new file mode 100644 index 0000000..6e1fbae --- /dev/null +++ b/src/test/java/org/springframework/test/web/client/MockClientHttpRequestFactoryTests.java @@ -0,0 +1,101 @@ +/* + * Copyright 2011-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.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.client.match.RequestMatchers.anything; + +import java.net.URI; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +/** + * Tests for {@link MockClientHttpRequestFactory}. + * + * @author Rossen Stoyanchev + */ +public class MockClientHttpRequestFactoryTests { + + private MockRestServiceServer server; + + private ClientHttpRequestFactory factory; + + + @Before + public void setup() { + RestTemplate restTemplate = new RestTemplate(); + this.server = MockRestServiceServer.createServer(restTemplate); + this.factory = restTemplate.getRequestFactory(); + } + + @Test + public void createRequest() throws Exception { + URI uri = new URI("/foo"); + ClientHttpRequest expected = (ClientHttpRequest) this.server.expect(anything()); + ClientHttpRequest actual = this.factory.createRequest(uri, HttpMethod.GET); + + assertSame(expected, actual); + assertEquals(uri, actual.getURI()); + assertEquals(HttpMethod.GET, actual.getMethod()); + } + + @Test + public void noFurtherRequestsExpected() throws Exception { + try { + this.factory.createRequest(new URI("/foo"), HttpMethod.GET); + } + catch (AssertionError error) { + assertEquals("No further requests expected", error.getMessage()); + } + } + + @Test + public void verifyZeroExpected() throws Exception { + this.server.verify(); + } + + @Test + public void verifyExpectedEqualExecuted() throws Exception { + this.server.expect(anything()); + this.server.expect(anything()); + + this.factory.createRequest(new URI("/foo"), HttpMethod.GET); + this.factory.createRequest(new URI("/bar"), HttpMethod.POST); + } + + @Test + public void verifyMoreExpected() throws Exception { + this.server.expect(anything()); + this.server.expect(anything()); + + this.factory.createRequest(new URI("/foo"), HttpMethod.GET); + + try { + this.server.verify(); + } + catch (AssertionError error) { + assertTrue(error.getMessage(), error.getMessage().contains("1 out of 2 were executed")); + } + } + +} diff --git a/src/test/java/org/springframework/test/web/client/match/ContentRequestMatchersTests.java b/src/test/java/org/springframework/test/web/client/match/ContentRequestMatchersTests.java new file mode 100644 index 0000000..3018bdf --- /dev/null +++ b/src/test/java/org/springframework/test/web/client/match/ContentRequestMatchersTests.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-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.client.match; + +import static org.hamcrest.Matchers.hasXPath; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.mock.http.client.MockClientHttpRequest; + +/** + * Tests for {@link ContentRequestMatchers}. + * + * @author Rossen Stoyanchev + */ +public class ContentRequestMatchersTests { + + private MockClientHttpRequest request; + + @Before + public void setUp() { + this.request = new MockClientHttpRequest(); + } + + @Test + public void testContentType() throws Exception { + this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON); + + RequestMatchers.content().mimeType("application/json").match(this.request); + RequestMatchers.content().mimeType(MediaType.APPLICATION_JSON).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testContentTypeNoMatch1() throws Exception { + this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON); + + RequestMatchers.content().mimeType("application/xml").match(this.request); + } + + @Test(expected=AssertionError.class) + public void testContentTypeNoMatch2() throws Exception { + this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON); + + RequestMatchers.content().mimeType(MediaType.APPLICATION_ATOM_XML).match(this.request); + } + + @Test + public void testString() throws Exception { + this.request.getBody().write("test".getBytes()); + + RequestMatchers.content().string("test").match(this.request); + } + + @Test(expected=AssertionError.class) + public void testStringNoMatch() throws Exception { + this.request.getBody().write("test".getBytes()); + + RequestMatchers.content().string("Test").match(this.request); + } + + @Test + public void testBytes() throws Exception { + byte[] content = "test".getBytes(); + this.request.getBody().write(content); + + RequestMatchers.content().bytes(content).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testBytesNoMatch() throws Exception { + this.request.getBody().write("test".getBytes()); + + RequestMatchers.content().bytes("Test".getBytes()).match(this.request); + } + + @Test + public void testXml() throws Exception { + String content = " "; + this.request.getBody().write(content.getBytes()); + + RequestMatchers.content().xml(content).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testXmlNoMatch() throws Exception { + this.request.getBody().write(" baz bazz 11 ".getBytes()); + + RequestMatchers.content().xml("22 ").match(this.request); + } + + @Test + public void testNodeMatcher() throws Exception { + String content = ""; + this.request.getBody().write(content.getBytes()); + + RequestMatchers.content().node(hasXPath("/foo/bar")).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testNodeMatcherNoMatch() throws Exception { + String content = " baz "; + this.request.getBody().write(content.getBytes()); + + RequestMatchers.content().node(hasXPath("/foo/bar/bar")).match(this.request); + } + +} diff --git a/src/test/java/org/springframework/test/web/client/match/JsonPathRequestMatchersTests.java b/src/test/java/org/springframework/test/web/client/match/JsonPathRequestMatchersTests.java new file mode 100644 index 0000000..e05a5c6 --- /dev/null +++ b/src/test/java/org/springframework/test/web/client/match/JsonPathRequestMatchersTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-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.client.match; + +import java.io.IOException; + +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.http.client.MockClientHttpRequest; + +/** + * Tests for {@link JsonPathRequestMatchers}. + * + * @author Rossen Stoyanchev + */ +public class JsonPathRequestMatchersTests { + + private static final String RESPONSE_CONTENT = "{\"foo\":\"bar\", \"qux\":[\"baz1\",\"baz2\"]}"; + + private MockClientHttpRequest request; + + @Before + public void setUp() throws IOException { + this.request = new MockClientHttpRequest(); + this.request.getBody().write(RESPONSE_CONTENT.getBytes()); + } + + @Test + public void value() throws Exception { + new JsonPathRequestMatchers("$.foo").value("bar").match(this.request); + } + + @Test(expected=AssertionError.class) + public void valueNoMatch() throws Exception { + new JsonPathRequestMatchers("$.foo").value("bogus").match(this.request); + } + + @Test + public void valueMatcher() throws Exception { + new JsonPathRequestMatchers("$.foo").value(Matchers.equalTo("bar")).match(this.request); + } + + @Test(expected=AssertionError.class) + public void valueMatcherNoMatch() throws Exception { + new JsonPathRequestMatchers("$.foo").value(Matchers.equalTo("bogus")).match(this.request); + } + + @Test + public void exists() throws Exception { + new JsonPathRequestMatchers("$.foo").exists().match(this.request); + } + + @Test(expected=AssertionError.class) + public void existsNoMatch() throws Exception { + new JsonPathRequestMatchers("$.bogus").exists().match(this.request); + } + + @Test + public void doesNotExist() throws Exception { + new JsonPathRequestMatchers("$.bogus").doesNotExist().match(this.request); + } + + @Test(expected=AssertionError.class) + public void doesNotExistNoMatch() throws Exception { + new JsonPathRequestMatchers("$.foo").doesNotExist().match(this.request); + } + + @Test + public void isArrayMatch() throws Exception { + new JsonPathRequestMatchers("$.qux").isArray().match(this.request); + } + + @Test(expected=AssertionError.class) + public void isArrayNoMatch() throws Exception { + new JsonPathRequestMatchers("$.bar").isArray().match(this.request); + } + +} diff --git a/src/test/java/org/springframework/test/web/client/match/RequestMatchersTests.java b/src/test/java/org/springframework/test/web/client/match/RequestMatchersTests.java new file mode 100644 index 0000000..afca10d --- /dev/null +++ b/src/test/java/org/springframework/test/web/client/match/RequestMatchersTests.java @@ -0,0 +1,138 @@ +/* + * Copyright 2011-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.client.match; + +import static org.hamcrest.Matchers.containsString; + +import java.net.URI; +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpMethod; +import org.springframework.mock.http.client.MockClientHttpRequest; + +/** + * Tests for {@link RequestMatchers}. + * + * @author Craig Walls + * @author Rossen Stoyanchev + */ +public class RequestMatchersTests { + + private MockClientHttpRequest request; + + @Before + public void setUp() { + this.request = new MockClientHttpRequest(); + } + + @Test + public void requestTo() throws Exception { + this.request.setURI(new URI("http://foo.com/bar")); + + RequestMatchers.requestTo("http://foo.com/bar").match(this.request); + } + + @Test(expected=AssertionError.class) + public void requestToNoMatch() throws Exception { + this.request.setURI(new URI("http://foo.com/bar")); + + RequestMatchers.requestTo("http://foo.com/wrong").match(this.request); + } + + @Test + public void requestToContains() throws Exception { + this.request.setURI(new URI("http://foo.com/bar")); + + RequestMatchers.requestTo(containsString("bar")).match(this.request); + } + + @Test + public void method() throws Exception { + this.request.setMethod(HttpMethod.GET); + + RequestMatchers.method(HttpMethod.GET).match(this.request); + } + + @Test(expected=AssertionError.class) + public void methodNoMatch() throws Exception { + this.request.setMethod(HttpMethod.POST); + + RequestMatchers.method(HttpMethod.GET).match(this.request); + } + + @Test + public void header() throws Exception { + this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); + + RequestMatchers.header("foo", "bar", "baz").match(this.request); + } + + @Test(expected=AssertionError.class) + public void headerMissing() throws Exception { + RequestMatchers.header("foo", "bar").match(this.request); + } + + @Test(expected=AssertionError.class) + public void headerMissingValue() throws Exception { + this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); + + RequestMatchers.header("foo", "bad").match(this.request); + } + + @SuppressWarnings("unchecked") + @Test + public void headerContains() throws Exception { + this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); + + RequestMatchers.header("foo", containsString("ba")).match(this.request); + } + + @SuppressWarnings("unchecked") + @Test(expected=AssertionError.class) + public void headerContainsWithMissingHeader() throws Exception { + RequestMatchers.header("foo", containsString("baz")).match(this.request); + } + + @SuppressWarnings("unchecked") + @Test(expected=AssertionError.class) + public void headerContainsWithMissingValue() throws Exception { + this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); + + RequestMatchers.header("foo", containsString("bx")).match(this.request); + } + + @Test + public void headers() throws Exception { + this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); + + RequestMatchers.header("foo", "bar", "baz").match(this.request); + } + + @Test(expected=AssertionError.class) + public void headersWithMissingHeader() throws Exception { + RequestMatchers.header("foo", "bar").match(this.request); + } + + @Test(expected=AssertionError.class) + public void headersWithMissingValue() throws Exception { + this.request.getHeaders().put("foo", Arrays.asList("bar")); + + RequestMatchers.header("foo", "bar", "baz").match(this.request); + } + +} \ No newline at end of file diff --git a/src/test/java/org/springframework/test/web/client/match/XpathRequestMatchersTests.java b/src/test/java/org/springframework/test/web/client/match/XpathRequestMatchersTests.java new file mode 100644 index 0000000..823d220 --- /dev/null +++ b/src/test/java/org/springframework/test/web/client/match/XpathRequestMatchersTests.java @@ -0,0 +1,112 @@ +/* + * Copyright 2002-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.client.match; + +import java.io.IOException; + +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.http.client.MockClientHttpRequest; + +/** + * Tests for {@link XpathRequestMatchers}. + * + * @author Rossen Stoyanchev + */ +public class XpathRequestMatchersTests { + + private static final String RESPONSE_CONTENT = " baz "; + + private MockClientHttpRequest request; + + @Before + public void setUp() throws IOException { + this.request = new MockClientHttpRequest(); + this.request.getBody().write(RESPONSE_CONTENT.getBytes()); + } + + @Test + public void testNodeMatcher() throws Exception { + new XpathRequestMatchers("/foo/bar", null).node(Matchers.notNullValue()).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testNodeMatcherNoMatch() throws Exception { + new XpathRequestMatchers("/foo/bar", null).node(Matchers.nullValue()).match(this.request); + } + + @Test + public void testExists() throws Exception { + new XpathRequestMatchers("/foo/bar", null).exists().match(this.request); + } + + @Test(expected=AssertionError.class) + public void testExistsNoMatch() throws Exception { + new XpathRequestMatchers("/foo/Bar", null).exists().match(this.request); + } + + @Test + public void testDoesNotExist() throws Exception { + new XpathRequestMatchers("/foo/Bar", null).doesNotExist().match(this.request); + } + + @Test(expected=AssertionError.class) + public void testDoesNotExistNoMatch() throws Exception { + new XpathRequestMatchers("/foo/bar", null).doesNotExist().match(this.request); + } + + @Test + public void testNodeCount() throws Exception { + new XpathRequestMatchers("/foo/bar", null).nodeCount(2).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testNodeCountNoMatch() throws Exception { + new XpathRequestMatchers("/foo/bar", null).nodeCount(1).match(this.request); + } + + @Test + public void testString() throws Exception { + new XpathRequestMatchers("/foo/bar[1]", null).string("111").match(this.request); + } + + @Test(expected=AssertionError.class) + public void testStringNoMatch() throws Exception { + new XpathRequestMatchers("/foo/bar[1]", null).string("112").match(this.request); + } + + @Test + public void testNumber() throws Exception { + new XpathRequestMatchers("/foo/bar[1]", null).number(111.0).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testNumberNoMatch() throws Exception { + new XpathRequestMatchers("/foo/bar[1]", null).number(111.1).match(this.request); + } + + @Test + public void testBoolean() throws Exception { + new XpathRequestMatchers("/foo/bar[2]", null).booleanValue(true).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testBooleanNoMatch() throws Exception { + new XpathRequestMatchers("/foo/bar[2]", null).booleanValue(false).match(this.request); + } + +} diff --git a/src/test/java/org/springframework/test/web/client/response/ResponseCreatorsTests.java b/src/test/java/org/springframework/test/web/client/response/ResponseCreatorsTests.java new file mode 100644 index 0000000..dd90d58 --- /dev/null +++ b/src/test/java/org/springframework/test/web/client/response/ResponseCreatorsTests.java @@ -0,0 +1,128 @@ +/* + * 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.client.response; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.net.URI; + +import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.mock.http.client.MockClientHttpResponse; +import org.springframework.util.FileCopyUtils; + +/** + * Tests for the {@link ResponseCreators} static factory methods. + * + * @author Rossen Stoyanchev + */ +public class ResponseCreatorsTests { + + @Test + public void success() throws Exception { + MockClientHttpResponse response = (MockClientHttpResponse) ResponseCreators.withSuccess().createResponse(null); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertTrue(response.getHeaders().isEmpty()); + assertNull(response.getBody()); + } + + @Test + public void successWithContent() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withSuccess("foo", MediaType.TEXT_PLAIN); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(MediaType.TEXT_PLAIN, response.getHeaders().getContentType()); + assertArrayEquals("foo".getBytes(), FileCopyUtils.copyToByteArray(response.getBody())); + } + + @Test + public void successWithContentWithoutContentType() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withSuccess("foo", null); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNull(response.getHeaders().getContentType()); + assertArrayEquals("foo".getBytes(), FileCopyUtils.copyToByteArray(response.getBody())); + } + + @Test + public void created() throws Exception { + URI location = new URI("/foo"); + DefaultResponseCreator responseCreator = ResponseCreators.withCreatedEntity(location); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertEquals(location, response.getHeaders().getLocation()); + assertNull(response.getBody()); + } + + @Test + public void noContent() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withNoContent(); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + assertTrue(response.getHeaders().isEmpty()); + assertNull(response.getBody()); + } + + @Test + public void badRequest() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withBadRequest(); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertTrue(response.getHeaders().isEmpty()); + assertNull(response.getBody()); + } + + @Test + public void unauthorized() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withUnauthorizedRequest(); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertTrue(response.getHeaders().isEmpty()); + assertNull(response.getBody()); + } + + @Test + public void serverError() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withServerError(); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); + assertTrue(response.getHeaders().isEmpty()); + assertNull(response.getBody()); + } + + @Test + public void withStatus() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withStatus(HttpStatus.FORBIDDEN); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode()); + assertTrue(response.getHeaders().isEmpty()); + assertNull(response.getBody()); + } + +} diff --git a/src/test/java/org/springframework/test/web/client/samples/SampleTests.java b/src/test/java/org/springframework/test/web/client/samples/SampleTests.java new file mode 100644 index 0000000..fcc93a3 --- /dev/null +++ b/src/test/java/org/springframework/test/web/client/samples/SampleTests.java @@ -0,0 +1,116 @@ +/* + * Copyright 2011-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.client.samples; + +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.client.match.RequestMatchers.method; +import static org.springframework.test.web.client.match.RequestMatchers.requestTo; +import static org.springframework.test.web.client.response.ResponseCreators.withSuccess; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.test.web.Person; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +/** + * Examples to demonstrate writing client-side REST tests with Spring MVC Test. + * While the tests in this class invoke the RestTemplate directly, in actual + * tests the RestTemplate may likely be invoked indirectly, i.e. through client + * code. + * + * @author Rossen Stoyanchev + */ +public class SampleTests { + + private MockRestServiceServer mockServer; + + private RestTemplate restTemplate; + + @Before + public void setup() { + this.restTemplate = new RestTemplate(); + this.mockServer = MockRestServiceServer.createServer(this.restTemplate); + } + + @Test + public void performGet() throws Exception { + + String responseBody = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}"; + + this.mockServer.expect(requestTo("/composers/42")).andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); + + @SuppressWarnings("unused") + Person ludwig = restTemplate.getForObject("/composers/{id}", Person.class, 42); + + // person.getName().equals("Ludwig van Beethoven") + // person.getDouble().equals(1.6035) + + this.mockServer.verify(); + } + + @Test + public void performGetWithResponseBodyFromFile() throws Exception { + + Resource responseBody = new ClassPathResource("ludwig.json", this.getClass()); + + this.mockServer.expect(requestTo("/composers/42")).andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); + + @SuppressWarnings("unused") + Person ludwig = restTemplate.getForObject("/composers/{id}", Person.class, 42); + + // hotel.getId() == 42 + // hotel.getName().equals("Holiday Inn") + + this.mockServer.verify(); + } + + @Test + public void verify() { + + this.mockServer.expect(requestTo("/number")).andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess("1", MediaType.TEXT_PLAIN)); + + this.mockServer.expect(requestTo("/number")).andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess("2", MediaType.TEXT_PLAIN)); + + this.mockServer.expect(requestTo("/number")).andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess("4", MediaType.TEXT_PLAIN)); + + this.mockServer.expect(requestTo("/number")).andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess("8", MediaType.TEXT_PLAIN)); + + @SuppressWarnings("unused") + String result = this.restTemplate.getForObject("/number", String.class); + // result == "1" + + result = this.restTemplate.getForObject("/number", String.class); + // result == "2" + + try { + this.mockServer.verify(); + } + catch (AssertionError error) { + assertTrue(error.getMessage(), error.getMessage().contains("2 out of 4 were executed")); + } + } +} diff --git a/src/test/java/org/springframework/test/web/client/samples/matchers/ContentRequestMatcherTests.java b/src/test/java/org/springframework/test/web/client/samples/matchers/ContentRequestMatcherTests.java new file mode 100644 index 0000000..5aea1f0 --- /dev/null +++ b/src/test/java/org/springframework/test/web/client/samples/matchers/ContentRequestMatcherTests.java @@ -0,0 +1,103 @@ +/* + * Copyright 2011-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.client.samples.matchers; + +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.client.match.RequestMatchers.content; +import static org.springframework.test.web.client.response.ResponseCreators.withSuccess; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; +import org.springframework.test.web.Person; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +/** + * Examples of defining expectations on request content and content type. + * + * @author Rossen Stoyanchev + * + * @see JsonPathRequestMatcherTests + * @see XmlContentRequestMatcherTests + * @see XpathRequestMatcherTests + */ +public class ContentRequestMatcherTests { + + private MockRestServiceServer mockServer; + + private RestTemplate restTemplate; + + @Before + public void setup() { + List 111 true > converters = new ArrayList >(); + converters.add(new StringHttpMessageConverter()); + converters.add(new MappingJacksonHttpMessageConverter()); + + this.restTemplate = new RestTemplate(); + this.restTemplate.setMessageConverters(converters); + + this.mockServer = MockRestServiceServer.createServer(this.restTemplate); + } + + @Test + public void contentType() throws Exception { + this.mockServer.expect(content().mimeType("application/json;charset=UTF-8")).andRespond(withSuccess()); + this.restTemplate.put(new URI("/foo"), new Person()); + this.mockServer.verify(); + } + + @Test + public void contentTypeNoMatch() throws Exception { + this.mockServer.expect(content().mimeType("application/json;charset=UTF-8")).andRespond(withSuccess()); + try { + this.restTemplate.put(new URI("/foo"), "foo"); + } + catch (AssertionError error) { + String message = error.getMessage(); + assertTrue(message, message.startsWith("Content type expected: ")); + } + } + + @Test + public void contentAsString() throws Exception { + this.mockServer.expect(content().string("foo")).andRespond(withSuccess()); + this.restTemplate.put(new URI("/foo"), "foo"); + this.mockServer.verify(); + } + + @Test + public void contentStringStartsWith() throws Exception { + this.mockServer.expect(content().string(startsWith("foo"))).andRespond(withSuccess()); + this.restTemplate.put(new URI("/foo"), "foo123"); + this.mockServer.verify(); + } + + @Test + public void contentAsBytes() throws Exception { + this.mockServer.expect(content().bytes("foo".getBytes())).andRespond(withSuccess()); + this.restTemplate.put(new URI("/foo"), "foo"); + this.mockServer.verify(); + } + +} diff --git a/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatcherTests.java b/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatcherTests.java new file mode 100644 index 0000000..f9b55a8 --- /dev/null +++ b/src/test/java/org/springframework/test/web/client/samples/matchers/HeaderRequestMatcherTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2011-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.client.samples.matchers; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.client.match.RequestMatchers.header; +import static org.springframework.test.web.client.match.RequestMatchers.requestTo; +import static org.springframework.test.web.client.response.ResponseCreators.withSuccess; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; +import org.springframework.test.web.Person; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +/** + * Examples of defining expectations on request headers. + * + * @author Rossen Stoyanchev + */ +public class HeaderRequestMatcherTests { + + private static final String RESPONSE_BODY = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}"; + + private MockRestServiceServer mockServer; + + private RestTemplate restTemplate; + + @Before + public void setup() { + List > converters = new ArrayList