diff --git a/README.md b/README.md index da036a8..821e365 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,27 @@ -Spring MVC Test Support -======================= +This project facilitates testing _Spring MVC_ server-side and client-side _RestTemplate_-based code. -The goal of this project is to faciliate the creation of integration tests for _Spring MVC_ applications. At present it contains server-side support only but will have client-side support added as well. +__NOTE: The project is now incorporated in the spring-test module of Spring Framework 3.2. Applications building against Spring Framework 3.1.x can continue to use this standalone project. However, applications building with Spring Framework 3.2 should use the spring-test module of Spring Framework 3.2 instead. See the Spring Framework [reference guide](http://static.springsource.org/spring-framework/docs/3.2.0.BUILD-SNAPSHOT/reference/htmlsingle/#spring-mvc-test-framework) for more details.__ -This code is intended for inclusion in the `spring-test` module of the __Spring Framework__. Its present home here allows us to evolve it on a flexible release schedule and with community feedback potentially accommodating a wide range of scenarios. +To get started, see sample [server-side](spring-test-mvc/tree/master/src/test/java/org/springframework/test/web/server/samples) and [client-side](spring-test-mvc/tree/master/src/test/java/org/springframework/test/web/client/samples) tests. The [spring-mvc-showcase](https://github.com/SpringSource/spring-mvc-showcase) project also has many sample tests. -Server-Side -=========== +Milestone 2 can be obtained through the +http://repo.springsource.org/libs-milestone repository. -Overview --------- -Annotated-controllers depend on Spring MVC to handle many things such as mapping requests, performing data binding and validation, setting the response status, writing to the body of the response using the correct content type, and many more. + + org.springframework + spring-test-mvc + 1.0.0.M2 + test + -To test all that you may instantiate an in-memory Servlet container driving requests with _JWebUnit_ or you may use a test tool such as _JMeter_ or _Selenium_. These options however require running a Servlet container and can only perform black-box testing. +The latest snapshot can be obtained through the http://repo.springsource.org/libs-snapshot repository. -The aim of this project is to provide a more "lightweight" and more integrated alternative by building on the familiar `MockHttpServletRequest` and the `MockHttpServletResponse` from the `spring-test` module and without the need for a Servlet container. Whether you want to point to one controller or to test with your complete web application context setup, it should be easy to send a request and verify the results. - -Examples --------- - -Test an `@ResponseBody` method in a controller: - - MockMvcBuilders.standaloneMvcSetup(new TestController()).build() - .perform(get("/form")) - .andExpect(status(200)) - .andExpect(contentType("text/plain")) - .andExpect(responseBody("content")); - -Test binding failure by pointing to Spring MVC XML-based context configuration: - - MockMvcBuilders.xmlConfigMvcSetup("classpath:org/examples/servlet-context.xml").build() - .perform(get("/form")) - .andExpect(status(200)) - .andExpect(modelAttributesWithErrors("formBean")) - .andExpect(viewName("form")); - -Test serving a resource by pointing to Spring MVC Java-based application configuration: - - MockMvcBuilders.annotationConfigMvcSetup(TestConfiguration.class).build() - .perform(get("/resources/Spring.js")) - .andExpect(contentType("application/octet-stream")) - .andExpect(responseBodyContains("Spring={};")); - -For more examples see tests in the [org.springframework.test.web.server](spring-test-mvc/tree/master/src/test/java/org/springframework/test/web/server) package. - -Limitations ------------ - -Most rendering technologies should work as expected. For _Tiles_ and _JSP_, while you can test with your existing configuration as is, no actual JSP-based rendering will take place. Instead you should verify the path the request was forwarded to (i.e. the path to the JSP page) or you can also verify the selected view name. - -Contributions -============= - -If you see anything you'd like to change we encourage taking advantage of github's social coding features by making the change in a [fork of this repository](http://help.github.com/forking/) and sending a pull request. - -To report an issue the Spring Framework forum or the Spring JIRA creating requests under the component _"SpringTEST"_. - -Before we accept a non-trivial patch or pull request we will need you to sign the [contributor's agreement] (https://support.springsource.com/spring_committer_signup). Signing the contributor's agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. Active contributors might be asked to join the core team, and given the ability to merge pull requests. - -Acknowledgements -================ - -This project draws inspiration from similar [server-side](http://static.springsource.org/spring-ws/sites/2.0/reference/html/server.html#d4e1487) and [client-side](http://static.springsource.org/spring-ws/sites/2.0/reference/html/client.html#d4e1860) test support introduced in Spring Web Services 2.0. + + org.springframework + spring-test-mvc + 1.0.0.BUILD-SNAPSHOT + test + +This project is available under version 2.0 of the [Apache License](http://www.apache.org/licenses/LICENSE-2.0). diff --git a/pom.xml b/pom.xml index b4a9dd4..f3aa3a9 100644 --- a/pom.xml +++ b/pom.xml @@ -1,31 +1,16 @@ - - 4.0.0 - - org.springframework - spring-test-mvc - Test support for Spring MVC applications - 1.0.0.BUILD-SNAPSHOT + + 4.0.0 + org.springframework + spring-test-mvc + Client and Server-Side Spring MVC Test Support + 1.0.0.BUILD-SNAPSHOT - 3.1.0.BUILD-SNAPSHOT + 3.1.2.RELEASE + 3.1.2.RELEASE - - - - maven-compiler-plugin - 2.1 - - 1.5 - 1.5 - - - - - org.springframework @@ -48,56 +33,73 @@ 2.5 provided + + org.hamcrest + hamcrest-all + 1.1 + + + com.jayway.jsonpath + json-path + 0.8.1 + true + + + xmlunit + xmlunit + 1.2 + true + - + junit junit - 4.8.1 - test - - - org.hamcrest - hamcrest-all - 1.1 - test - - - org.slf4j - slf4j-api - 1.5.10 - test - + 4.10 + test + + + org.hamcrest + hamcrest-core + + + + + org.slf4j + slf4j-api + 1.5.10 + test + org.slf4j slf4j-log4j12 1.5.10 test - - log4j - log4j - 1.2.15 - test - - - javax.mail - mail - - - javax.jms - jms - - - com.sun.jdmk - jmxtools - - - com.sun.jmx - jmxri - - - + + log4j + log4j + 1.2.15 + test + + + javax.mail + mail + + + javax.jms + jms + + + com.sun.jdmk + jmxtools + + + com.sun.jmx + jmxri + + + javax.servlet jstl @@ -114,13 +116,13 @@ commons-logging commons-logging-api - + org.hibernate hibernate-validator - 4.0.2.GA + 4.2.0.Final test @@ -147,13 +149,53 @@ 2.2 test + + rome + rome + 1.0 + test + + + javax.xml.bind + jaxb-api + 2.2.6 + test + + + org.springframework.security + spring-security-core + ${spring.security.version} + test + + + org.springframework.security + spring-security-web + ${spring.security.version} + test + + + spring-jdbc + org.springframework + + + spring-tx + org.springframework + + + + + org.springframework.security + spring-security-config + ${spring.security.version} + test + - org.springframework.maven.snapshot - Spring Maven Snapshot Repository - http://maven.springframework.org/snapshot + spring-libs-snapshot + Spring project release, milestone, and snapshot artifacts and transitive dependencies + http://repo.springsource.org/libs-snapshot false @@ -163,28 +205,81 @@ - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.7 - - - org.apache.maven.plugins - maven-surefire-plugin - - - **/*Tests.java - - - **/Abstract*.java - - junit:junit - -Xmx512m - - - - - + + + + maven-compiler-plugin + 2.1 + + 1.5 + 1.5 + + + + org.apache.maven.plugins + maven-source-plugin + 2.1.2 + + + package + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + **/*Tests.java + + + **/Abstract*.java + + junit:junit + -Xmx512m + + + + org.codehaus.mojo + animal-sniffer-maven-plugin + 1.8 + + + org.codehaus.mojo.signature + java15 + 1.0 + + + + + animal-sniffer + test + + check + + + + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.7 + + + org.apache.maven.plugins + maven-surefire-report-plugin + 2.12 + + + + diff --git a/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java b/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java new file mode 100644 index 0000000..5de14e9 --- /dev/null +++ b/src/main/java/org/springframework/mock/http/MockHttpInputMessage.java @@ -0,0 +1,56 @@ +/* + * 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.mock.http; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpInputMessage; +import org.springframework.util.Assert; + +/** + * Mock implementation of {@link HttpInputMessage}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class MockHttpInputMessage implements HttpInputMessage { + + private final HttpHeaders headers = new HttpHeaders(); + + private final InputStream body; + + + public MockHttpInputMessage(byte[] contents) { + this.body = (contents != null) ? new ByteArrayInputStream(contents) : null; + } + + public MockHttpInputMessage(InputStream body) { + Assert.notNull(body, "'body' must not be null"); + this.body = body; + } + + public HttpHeaders getHeaders() { + return this.headers; + } + + public InputStream getBody() throws IOException { + return this.body; + } + +} diff --git a/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java b/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java new file mode 100644 index 0000000..2d40a0a --- /dev/null +++ b/src/main/java/org/springframework/mock/http/MockHttpOutputMessage.java @@ -0,0 +1,83 @@ +/* + * 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.mock.http; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpOutputMessage; + +/** + * Mock implementation of {@link HttpOutputMessage}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class MockHttpOutputMessage implements HttpOutputMessage { + + private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + private final HttpHeaders headers = new HttpHeaders(); + + private final ByteArrayOutputStream body = new ByteArrayOutputStream(); + + + public HttpHeaders getHeaders() { + return this.headers; + } + + /** + * Return the body content. + */ + public OutputStream getBody() throws IOException { + return this.body; + } + + /** + * Return body content as a byte array. + */ + public byte[] getBodyAsBytes() { + return this.body.toByteArray(); + } + + /** + * Return the body content interpreted as a UTF-8 string. + */ + public String getBodyAsString() { + return getBodyAsString(DEFAULT_CHARSET); + } + + /** + * Return the body content as a string. + * @param charset the charset to use to turn the body content to a String + */ + public String getBodyAsString(Charset charset) { + byte[] bytes = getBodyAsBytes(); + try { + // Use + return new String(bytes, charset.name()); + } + catch (UnsupportedEncodingException ex) { + // should not occur + throw new InternalError(ex.getMessage()); + } + } + +} diff --git a/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java b/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java new file mode 100644 index 0000000..e87977e --- /dev/null +++ b/src/main/java/org/springframework/mock/http/client/MockClientHttpRequest.java @@ -0,0 +1,111 @@ +/* + * 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.mock.http.client; + +import java.io.IOException; +import java.net.URI; + +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.mock.http.MockHttpOutputMessage; + +/** + * Mock implementation of {@link ClientHttpRequest}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class MockClientHttpRequest extends MockHttpOutputMessage implements ClientHttpRequest { + + private URI uri; + + private HttpMethod httpMethod; + + private boolean executed = false; + + private ClientHttpResponse clientHttpResponse; + + + /** + * Default constructor. + */ + public MockClientHttpRequest() { + } + + /** + * Create an instance with the given HttpMethod and URI. + */ + public MockClientHttpRequest(HttpMethod httpMethod, URI uri) { + this.httpMethod = httpMethod; + this.uri = uri; + } + + public URI getURI() { + return this.uri; + } + + public void setURI(URI uri) { + this.uri = uri; + } + + public HttpMethod getMethod() { + return this.httpMethod; + } + + public void setMethod(HttpMethod httpMethod) { + this.httpMethod = httpMethod; + } + + public void setResponse(ClientHttpResponse clientHttpResponse) { + this.clientHttpResponse = clientHttpResponse; + } + + /** + * Whether the execute method was invoked or not. + */ + public boolean isExecuted() { + return this.executed; + } + + /** + * Sets the {@link #isExecuted() executed} flag to true and returns the + * configured {@link #setResponse(ClientHttpResponse) response}. + */ + public ClientHttpResponse execute() throws IOException { + this.executed = true; + return this.clientHttpResponse; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (this.httpMethod != null) { + sb.append(this.httpMethod); + } + if (this.uri != null) { + sb.append(" ").append(this.uri); + } + if (!getHeaders().isEmpty()) { + sb.append(", headers : ").append(getHeaders()); + } + if (sb.length() == 0) { + sb.append("Not yet initialized"); + } + return sb.toString(); + } + +} diff --git a/src/main/java/org/springframework/mock/http/client/MockClientHttpResponse.java b/src/main/java/org/springframework/mock/http/client/MockClientHttpResponse.java new file mode 100644 index 0000000..9ed4077 --- /dev/null +++ b/src/main/java/org/springframework/mock/http/client/MockClientHttpResponse.java @@ -0,0 +1,70 @@ +/* + * 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.mock.http.client; + +import java.io.IOException; +import java.io.InputStream; + +import org.springframework.http.HttpStatus; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.mock.http.MockHttpInputMessage; +import org.springframework.util.Assert; + +/** + * Mock implementation of {@link ClientHttpResponse}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class MockClientHttpResponse extends MockHttpInputMessage implements ClientHttpResponse { + + private final HttpStatus status; + + + /** + * Constructor with response body as a byte array. + */ + public MockClientHttpResponse(byte[] body, HttpStatus statusCode) { + super(body); + Assert.notNull(statusCode, "statisCode is required"); + this.status = statusCode; + } + + /** + * Constructor with response body as InputStream. + */ + public MockClientHttpResponse(InputStream body, HttpStatus statusCode) { + super(body); + Assert.notNull(statusCode, "statisCode is required"); + this.status = statusCode; + } + + public HttpStatus getStatusCode() throws IOException { + return this.status; + } + + public int getRawStatusCode() throws IOException { + return this.status.value(); + } + + public String getStatusText() throws IOException { + return this.status.getReasonPhrase(); + } + + public void close() { + } + +} diff --git a/src/main/java/org/springframework/mock/http/client/package-info.java b/src/main/java/org/springframework/mock/http/client/package-info.java new file mode 100644 index 0000000..d4e07d5 --- /dev/null +++ b/src/main/java/org/springframework/mock/http/client/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-side HTTP abstractions. + * This package contains the 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, Map namedValues, String...names) { - for (String name : names) { - if (!namedValues.containsKey(name)) { - fail(label + " '" + name + "' was not found. Actual " + label.toLowerCase() + "s: <" + namedValues + ">."); - } - } - } - - /** - * Assert the given names are not 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 assertNameValuesNotPresent(String label, Map namedValues, String...names) { - for (String name : names) { - if (namedValues.containsKey(name)) { - fail(label + " '" + name + "' found but was not expected. Actual value: <" + namedValues.get(name) + ">."); - } - } - } } diff --git a/src/main/java/org/springframework/test/web/client/MockRestServiceServer.java b/src/main/java/org/springframework/test/web/client/MockRestServiceServer.java new file mode 100644 index 0000000..ddf1ba1 --- /dev/null +++ b/src/main/java/org/springframework/test/web/client/MockRestServiceServer.java @@ -0,0 +1,206 @@ +/* + * 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.net.URI; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.test.web.client.match.RequestMatchers; +import org.springframework.test.web.client.response.ResponseCreators; +import org.springframework.util.Assert; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.client.support.RestGatewaySupport; + +/** + * Main entry point for client-side REST testing. Used for tests + * that involve direct or indirect (through client code) use of the + * {@link RestTemplate}. Provides a way to set up fine-grained expectations + * on the requests that will be performed through the {@code RestTemplate} and + * a way to define the responses to send back removing the need for an + * actual running server. + * + *

Below 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 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 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 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... 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[] 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 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 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 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 initHandlerMappings(); - - protected abstract List initHandlerAdapters(); - - protected abstract List initHandlerExceptionResolvers(); - - protected abstract List 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 List defaultResultMatchers = 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, Map model) { - 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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: - *