diff --git a/build.gradle b/build.gradle index f5898654b6..034210e3a3 100644 --- a/build.gradle +++ b/build.gradle @@ -548,6 +548,48 @@ project('spring-test') { } } +project('spring-test-mvc') { + description = 'Spring Test MVC Framework' + apply from: 'test-mvc.gradle' + dependencies { + compile project(":spring-context") + compile project(":spring-webmvc") + compile project(":spring-test").sourceSets.main.output + compile("org.apache.tomcat:tomcat-servlet-api:7.0.8", provided) + compile "org.hamcrest:hamcrest-all:1.1" + compile("com.jayway.jsonpath:json-path:0.8.1", optional) + compile("xmlunit:xmlunit:1.2", optional) + testCompile("org.slf4j:jcl-over-slf4j:1.6.1") + testCompile("org.slf4j:slf4j-log4j12:1.6.1") { + exclude group: 'log4j', module: 'log4j' + } + testCompile("log4j:log4j:1.2.15") { + exclude group: 'javax.mail', module: 'mail' + exclude group: 'javax.jms', module: 'jms' + exclude group: 'com.sun.jdmk', module: 'jmxtools' + exclude group: 'com.sun.jmx', module: 'jmxri' + } + testCompile "javax.servlet:jstl:1.2" + testCompile "org.apache.tiles:tiles-jsp:2.2.2" + testCompile "org.hibernate:hibernate-validator:4.2.0.Final" + testCompile "org.codehaus.jackson:jackson-mapper-asl:1.4.2" + testCompile project(":spring-oxm") + testCompile "com.thoughtworks.xstream:xstream:1.3.1" + testCompile "cglib:cglib-nodep:2.2" + testCompile "rome:rome:1.0" + testCompile "javax.xml.bind:jaxb-api:2.2.6" + testCompile("org.springframework.security:spring-security-core:3.1.2.RELEASE") { + exclude group: 'org.springframework' + } + testCompile("org.springframework.security:spring-security-web:3.1.2.RELEASE") { + exclude group: 'org.springframework' + } + testCompile("org.springframework.security:spring-security-config:3.1.2.RELEASE") { + exclude group: 'org.springframework' + } + } +} + project('spring-struts') { description = 'Spring Struts' dependencies { diff --git a/merge-dist.gradle b/merge-dist.gradle new file mode 100644 index 0000000000..cf32667de9 --- /dev/null +++ b/merge-dist.gradle @@ -0,0 +1,72 @@ +import org.gradle.plugins.ide.eclipse.model.ProjectDependency + +/** + * Will merge the distributions of the current project into mergeIntoProject. For + * example, to bundle spring-test-mvc in spring-test's jars. This script will perform the + * following steps: + * + * + * Example Usage: + * + * ext.mergeIntoProject = project(':spring-test') + * apply from: "${rootProject.projectDir}/merge-dist.gradle" + */ + +def mergeFromProject = project + +// invoking a task on mergeFromProject will invoke the task with the same name on mergeIntoProject +def taskNamesToMerge = ['sourcesJar','jar','javadocJar','javadoc','install'] +taskNamesToMerge.each { taskName -> + def taskToRemove = tasks.getByPath(taskName) + taskToRemove.enabled = false + taskToRemove.dependsOn mergeIntoProject."$taskName" +} + +// update mergeIntoProject artifacts to contain the mergeFromProject artifact contents +mergeIntoProject."sourcesJar" { + from mergeFromProject.sourcesJar.source +} +mergeIntoProject."jar" { + from mergeFromProject.jar.source +} +mergeIntoProject."javadoc" { + source += mergeFromProject.javadoc.source + classpath += mergeFromProject.javadoc.classpath +} + +// GRADLE-1116 +mergeFromProject.eclipse.classpath.file.whenMerged { classpath -> + classpath.entries.removeAll { entry -> entry.path.contains("/${mergeIntoProject.name}/build/") } + def dependency = new ProjectDependency("/${mergeIntoProject.name}", mergeIntoProject.path) + dependency.exported = true + classpath.entries.add(dependency) +} + +// Update mergeIntoProject to contain additional configurations that contains all the dependencies from mergeFromProject +// so that Maven pom generation works +gradle.taskGraph.whenReady { + mergeFromProject.configurations.archives.artifacts.clear() + + mergeFromProject.configurations.each { config-> + def mapping = mergeFromProject.conf2ScopeMappings.getMapping([config]) + if(mapping.scope) { + def newConfigName = mergeFromProject.name + "-"+ config.name + mergeIntoProject.configurations.add(newConfigName) + config.dependencies.each { dependency -> + mergeIntoProject.dependencies.add(newConfigName, dependency) + } + configure(mergeIntoProject.install.repositories.mavenInstaller.pom.scopeMappings) { + addMapping(mapping.priority + 100, mergeIntoProject.configurations."$newConfigName", mapping.scope) + } + mergeIntoProject.optionalDeps += mergeFromProject.optionalDeps + mergeIntoProject.providedDeps += mergeFromProject.providedDeps + } + } +} diff --git a/settings.gradle b/settings.gradle index f882ae2634..1a4026d318 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,6 +15,7 @@ include 'spring-orm' include 'spring-oxm' include 'spring-struts' include 'spring-test' +include 'spring-test-mvc' include 'spring-tx' include 'spring-web' include 'spring-webmvc' diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/AssertionErrors.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/AssertionErrors.java new file mode 100644 index 0000000000..e1370d100c --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/AssertionErrors.java @@ -0,0 +1,79 @@ +/* + * 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.mock; + +/** + * JUnit independent assertion class. + * + * @author Lukas Krecan + * @author Arjen Poutsma + * @since 3.2 + */ +public abstract class AssertionErrors { + + private AssertionErrors() { + } + + /** + * Fails a test with the given message. + * + * @param message the message + */ + public static void fail(String message) { + throw new AssertionError(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 + */ + public static void fail(String message, Object expected, Object actual) { + throw new AssertionError(message + " expected:<" + expected + "> but was:<" + actual + ">"); + } + + /** + * Asserts that a condition is {@code true}. If not, throws an {@link AssertionError} with the given message. + * + * @param message the message + * @param condition the condition to test for + */ + public static void assertTrue(String message, boolean condition) { + if (!condition) { + fail(message); + } + } + + /** + * Asserts that two objects are equal. If not, an {@link AssertionError} is thrown with the given message. + * + * @param message the message + * @param expected the expected value + * @param actual the actual value + */ + public static void assertEquals(String message, Object expected, Object actual) { + if (expected == null && actual == null) { + return; + } + if (expected != null && expected.equals(actual)) { + return; + } + fail(message, expected, actual); + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/MockRestServiceServer.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/MockRestServiceServer.java new file mode 100644 index 0000000000..b454f5d545 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/MockRestServiceServer.java @@ -0,0 +1,207 @@ +/* + * 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.mock.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.mock.client.match.RequestMatchers; +import org.springframework.test.web.mock.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/RequestMatcher.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/RequestMatcher.java new file mode 100644 index 0000000000..d4b6611d1d --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/RequestMatcher.java @@ -0,0 +1,39 @@ +/* + * 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.mock.client; + +import java.io.IOException; + +import org.springframework.http.client.ClientHttpRequest; + +/** + * A contract for matching requests to expectations. + * + * @author Craig Walls + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/RequestMatcherClientHttpRequest.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/RequestMatcherClientHttpRequest.java new file mode 100644 index 0000000000..489f88a869 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/RequestMatcherClientHttpRequest.java @@ -0,0 +1,79 @@ +/* + * 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.mock.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/ResponseActions.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/ResponseActions.java new file mode 100644 index 0000000000..bd31fbfb0c --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/ResponseActions.java @@ -0,0 +1,39 @@ +/* + * 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.mock.client; + +/** + * A contract for setting up request expectations and defining a response. + * Implementations can be obtained through {@link MockRestServiceServer#expect(RequestMatcher)}. + * + * @author Craig Walls + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/ResponseCreator.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/ResponseCreator.java new file mode 100644 index 0000000000..400890903c --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/ResponseCreator.java @@ -0,0 +1,39 @@ +/* + * 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.mock.client; + +import java.io.IOException; + +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.test.web.mock.client.response.ResponseCreators; + +/** + * A contract for creating a {@link ClientHttpResponse}. + * Implementations can be obtained via {@link ResponseCreators}. + * + * @author Craig Walls + * @since 3.2 + */ +public interface ResponseCreator { + + /** + * Create a response for the given request. + * @param request the request + */ + ClientHttpResponse createResponse(ClientHttpRequest request) throws IOException; + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/ContentRequestMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/ContentRequestMatchers.java new file mode 100644 index 0000000000..85cd75b700 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/ContentRequestMatchers.java @@ -0,0 +1,170 @@ +/* + * 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.mock.client.match; + +import static org.springframework.test.web.mock.AssertionErrors.assertEquals; +import static org.springframework.test.web.mock.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.mock.client.RequestMatcher; +import org.springframework.test.web.mock.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/JsonPathRequestMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/JsonPathRequestMatchers.java new file mode 100644 index 0000000000..234290908f --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/JsonPathRequestMatchers.java @@ -0,0 +1,128 @@ +/* + * 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.mock.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.mock.client.RequestMatcher; +import org.springframework.test.web.mock.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/RequestMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/RequestMatchers.java new file mode 100644 index 0000000000..b5e9208880 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/RequestMatchers.java @@ -0,0 +1,263 @@ +/* + * 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.mock.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.mock.AssertionErrors; +import org.springframework.test.web.mock.client.MockRestServiceServer; +import org.springframework.test.web.mock.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/XpathRequestMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/XpathRequestMatchers.java new file mode 100644 index 0000000000..2c19caab57 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/XpathRequestMatchers.java @@ -0,0 +1,178 @@ +/* + * 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.mock.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.mock.client.RequestMatcher; +import org.springframework.test.web.mock.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/package-info.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/package-info.java new file mode 100644 index 0000000000..bd89bae279 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/match/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * Contains built-in {@link org.springframework.test.web.mock.client.RequestMatcher} + * implementations. Use + * {@link org.springframework.test.web.mock.client.match.RequestMatchers} + * to gain access to instances of those implementations. + */ +package org.springframework.test.web.mock.client.match; diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/package-info.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/package-info.java new file mode 100644 index 0000000000..0c2703aaa1 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * Contains client-side REST testing support. + * @see org.springframework.test.web.mock.client.MockRestServiceServer + */ +package org.springframework.test.web.mock.client; diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/response/DefaultResponseCreator.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/response/DefaultResponseCreator.java new file mode 100644 index 0000000000..92eb2449f7 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/response/DefaultResponseCreator.java @@ -0,0 +1,132 @@ +/* + * 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.mock.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.mock.client.ResponseCreator; +import org.springframework.util.Assert; + +/** + * A {@code ResponseCreator} with builder-style methods for adding response details. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/response/ResponseCreators.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/response/ResponseCreators.java new file mode 100644 index 0000000000..18982b4941 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/response/ResponseCreators.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.mock.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.mock.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 + * @since 3.2 + */ +public abstract class ResponseCreators { + + + 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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/response/package-info.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/response/package-info.java new file mode 100644 index 0000000000..d7c508aaac --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/client/response/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * Contains built-in {@link org.springframework.test.web.mock.client.ResponseCreator} + * implementations. Use + * {@link org.springframework.test.web.mock.client.response.ResponseCreators} + * to gain access to instances of those implementations. + */ +package org.springframework.test.web.mock.client.response; diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/DefaultMvcResult.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/DefaultMvcResult.java new file mode 100644 index 0000000000..391dd90bc1 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/DefaultMvcResult.java @@ -0,0 +1,99 @@ +/* + * 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.mock.servlet; + +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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/MockMvc.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/MockMvc.java new file mode 100644 index 0000000000..9feb7cb1d2 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/MockMvc.java @@ -0,0 +1,167 @@ +/* + * 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.mock.servlet; + +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletContext; + +import org.springframework.beans.Mergeable; +import org.springframework.mock.web.MockFilterChain; +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.*
+ *
+ * WebApplicationContext wac = ...;
+ *
+ * MockMvc mockMvc = webAppContextSetup(wac).configureWarRootDir("src/main/webapp", false).build()
+ *
+ * mockMvc.perform(get("/form"))
+ *     .andExpect(status().isOk())
+ *     .andExpect(content().mimeType("text/html"))
+ *     .andExpect(forwardedUrl("/WEB-INF/layouts/main.jsp"));
+ * 
+ * + * @author Rossen Stoyanchev + * @author Rob Winch + * @since 3.2 + */ +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.mock.servlet.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.mock.servlet.setup.DefaultMockMvcBuilder#defaultRequest(RequestBuilder) + */ + void setDefaultRequest(RequestBuilder requestBuilder) { + this.defaultRequestBuilder = requestBuilder; + } + + /** + * Expectations to assert after every performed request. + * @see org.springframework.test.web.mock.servlet.setup.DefaultMockMvcBuilder#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.mock.servlet.setup.DefaultMockMvcBuilder#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.mock.servlet.request.MockMvcRequestBuilders} + * + * @return an instance of {@link ResultActions}; never {@code null} + * + * @see org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders + * @see org.springframework.test.web.mock.servlet.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); + } + + for (ResultHandler handler : this.defaultResultHandlers) { + handler.handle(mvcResult); + } + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/MockMvcBuilder.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/MockMvcBuilder.java new file mode 100644 index 0000000000..d971366f00 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/MockMvcBuilder.java @@ -0,0 +1,35 @@ +/* + * 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.mock.servlet; + +/** + * Builds a {@link MockMvc}. + * + *

See static, factory methods in + * {@code org.springframework.test.web.server.setup.MockMvcBuilders}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public interface MockMvcBuilder { + + /** + * Build a {@link MockMvc} instance. + */ + MockMvc build(); + +} \ No newline at end of file diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/MockMvcBuilderSupport.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/MockMvcBuilderSupport.java new file mode 100644 index 0000000000..0a7431be1b --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/MockMvcBuilderSupport.java @@ -0,0 +1,77 @@ +/* + * 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.mock.servlet; + +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.MockFilterChain; +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.mock.servlet.setup.DefaultMockMvcBuilder}, + * 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 + * @since 3.2 + */ +public abstract class MockMvcBuilderSupport { + + protected final 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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/MvcResult.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/MvcResult.java new file mode 100644 index 0000000000..49c0b990c4 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/MvcResult.java @@ -0,0 +1,78 @@ +/* + * 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.mock.servlet; + +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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/RequestBuilder.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/RequestBuilder.java new file mode 100644 index 0000000000..ee11266b02 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/RequestBuilder.java @@ -0,0 +1,27 @@ +package org.springframework.test.web.mock.servlet; + +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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/ResultActions.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/ResultActions.java new file mode 100644 index 0000000000..59ea5d4770 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/ResultActions.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.test.web.mock.servlet; + +/** + * 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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/ResultHandler.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/ResultHandler.java new file mode 100644 index 0000000000..75b38fb687 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/ResultHandler.java @@ -0,0 +1,47 @@ +/* + * 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.mock.servlet; + +/** + * 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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/ResultMatcher.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/ResultMatcher.java new file mode 100644 index 0000000000..7a44bf35f2 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/ResultMatcher.java @@ -0,0 +1,48 @@ +/* + * 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.mock.servlet; + +/** + * 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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/TestDispatcherServlet.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/TestDispatcherServlet.java new file mode 100644 index 0000000000..606b3ee74d --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/TestDispatcherServlet.java @@ -0,0 +1,153 @@ +/* + * 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.mock.servlet; + +import java.io.IOException; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.async.CallableProcessingInterceptor; +import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.context.request.async.DeferredResultProcessingInterceptor; +import org.springframework.web.context.request.async.WebAsyncManager; +import org.springframework.web.context.request.async.WebAsyncUtils; +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 + * @since 3.2 + */ +@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 void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); + + TestCallableInterceptor callableInterceptor = new TestCallableInterceptor(); + asyncManager.registerCallableInterceptor("mock-mvc", callableInterceptor); + + TestDeferredResultInterceptor deferredResultInterceptor = new TestDeferredResultInterceptor(); + asyncManager.registerDeferredResultInterceptor("mock-mvc", deferredResultInterceptor); + + super.service(request, response); + + Object handler = getMvcResult(request).getHandler(); + if (asyncManager.isConcurrentHandlingStarted() && !deferredResultInterceptor.wasInvoked) { + if (!callableInterceptor.await()) { + throw new ServletException( + "Gave up waiting on Callable from [" + handler.getClass().getName() + "] to complete"); + } + } + } + + @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; + } + + + private final class TestCallableInterceptor implements CallableProcessingInterceptor { + + private final CountDownLatch latch = new CountDownLatch(1); + + private boolean await() { + try { + return this.latch.await(5, TimeUnit.SECONDS); + } + catch (InterruptedException e) { + return false; + } + } + + public void preProcess(NativeWebRequest request, Callable task) { } + + public void postProcess(NativeWebRequest request, Callable task, Object concurrentResult) { + this.latch.countDown(); + } + } + + private final class TestDeferredResultInterceptor implements DeferredResultProcessingInterceptor { + + private boolean wasInvoked; + + public void preProcess(NativeWebRequest request, DeferredResult deferredResult) { + this.wasInvoked = true; + } + + public void postProcess(NativeWebRequest request, DeferredResult deferredResult, Object concurrentResult) { } + + public void afterExpiration(NativeWebRequest request, DeferredResult deferredResult) { } + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/package-info.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/package-info.java new file mode 100644 index 0000000000..a90823f396 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ + +/** + * Contains server-side support for testing Spring MVC applications. + * @see org.springframework.test.web.mock.servlet.MockMvc + */ +package org.springframework.test.web.mock.servlet; diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/MockAsyncContext.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/MockAsyncContext.java new file mode 100644 index 0000000000..f5cec81e81 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/MockAsyncContext.java @@ -0,0 +1,131 @@ +/* + * 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.mock.servlet.request; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.springframework.beans.BeanUtils; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.web.util.WebUtils; + +/** + * Mock implementation of the {@link AsyncContext} interface. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +class MockAsyncContext implements AsyncContext { + + private final ServletRequest request; + + private final ServletResponse response; + + private final List listeners = new ArrayList(); + + private String dispatchedPath; + + private long timeout = 10 * 60 * 1000L; // 10 seconds is Tomcat's default + + + public MockAsyncContext(ServletRequest request, ServletResponse response) { + this.request = request; + this.response = response; + } + + public ServletRequest getRequest() { + return this.request; + } + + public ServletResponse getResponse() { + return this.response; + } + + public boolean hasOriginalRequestAndResponse() { + return (this.request instanceof MockHttpServletRequest) && (this.response instanceof MockHttpServletResponse); + } + + public String getDispatchedPath() { + return this.dispatchedPath; + } + + public void dispatch() { + dispatch(null); + } + + public void dispatch(String path) { + dispatch(null, path); + } + + public void dispatch(ServletContext context, String path) { + this.dispatchedPath = path; + } + + public void complete() { + Servlet3MockHttpServletRequest mockRequest = WebUtils.getNativeRequest(request, Servlet3MockHttpServletRequest.class); + if (mockRequest != null) { + mockRequest.setAsyncStarted(false); + } + + for (AsyncListener listener : this.listeners) { + try { + listener.onComplete(new AsyncEvent(this, this.request, this.response)); + } + catch (IOException e) { + throw new IllegalStateException("AsyncListener failed", e); + } + } + } + + public void start(Runnable runnable) { + runnable.run(); + } + + public List getListeners() { + return this.listeners; + } + + public void addListener(AsyncListener listener) { + this.listeners.add(listener); + } + + public void addListener(AsyncListener listener, ServletRequest request, ServletResponse response) { + this.listeners.add(listener); + } + + public T createListener(Class clazz) throws ServletException { + return BeanUtils.instantiateClass(clazz); + } + + public long getTimeout() { + return this.timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/MockHttpServletRequestBuilder.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/MockHttpServletRequestBuilder.java new file mode 100644 index 0000000000..9d2a4136cf --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/MockHttpServletRequestBuilder.java @@ -0,0 +1,694 @@ +/* + * 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.mock.servlet.request; + +import java.lang.reflect.Constructor; +import java.net.URI; +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.beans.BeanUtils; +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.mock.servlet.MockMvc; +import org.springframework.test.web.mock.servlet.RequestBuilder; +import org.springframework.test.web.mock.servlet.setup.MockMvcBuilders; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +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; + +/** + * 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 + * @since 3.2 +*/ +public class MockHttpServletRequestBuilder implements RequestBuilder, Mergeable { + + private final UriComponentsBuilder uriComponentsBuilder; + + 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 uri the URI for the request including any component (e.g. scheme, host, query) + * @param httpMethod the HTTP method for the request + */ + MockHttpServletRequestBuilder(URI uri, HttpMethod httpMethod) { + + Assert.notNull(uri, "uri is required"); + Assert.notNull(httpMethod, "httpMethod is required"); + + this.uriComponentsBuilder = UriComponentsBuilder.fromUri(uri); + 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); + + UriComponents uriComponents = this.uriComponentsBuilder.build(); + + String requestUri = uriComponents.getPath(); + request.setRequestURI(requestUri); + + updatePathRequestProperties(request, requestUri); + + if (uriComponents.getScheme() != null) { + request.setScheme(uriComponents.getScheme()); + } + if (uriComponents.getHost() != null) { + request.setServerName(uriComponents.getHost()); + } + if (uriComponents.getPort() != -1) { + request.setServerPort(uriComponents.getPort()); + } + + request.setMethod(this.method.name()); + + for (String name : this.headers.keySet()) { + for (Object value : this.headers.get(name)) { + request.addHeader(name, value); + } + } + + request.setQueryString(uriComponents.getQuery()); + + for (Entry> entry : uriComponents.getQueryParams().entrySet()) { + for (String value : entry.getValue()) { + request.addParameter(entry.getKey(), value); + } + } + + 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 ClassUtils.hasMethod(ServletRequest.class, "startAsync") ? + createServlet3Request(servletContext) : new MockHttpServletRequest(servletContext); + } + + private static MockHttpServletRequest createServlet3Request(ServletContext servletContext) { + try { + String className = "org.springframework.test.web.mock.servlet.request.Servlet3MockHttpServletRequest"; + Class clazz = ClassUtils.forName(className, MockHttpServletRequestBuilder.class.getClassLoader()); + Constructor constructor = clazz.getConstructor(ServletContext.class); + return (MockHttpServletRequest) BeanUtils.instantiateClass(constructor, servletContext); + } + catch (Throwable t) { + throw new IllegalStateException("Failed to instantiate MockHttpServletRequest", t); + } + } + + /** + * 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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/MockMultipartHttpServletRequestBuilder.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/MockMultipartHttpServletRequestBuilder.java new file mode 100644 index 0000000000..538ad92870 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/MockMultipartHttpServletRequestBuilder.java @@ -0,0 +1,121 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.web.mock.servlet.request; + +import java.lang.reflect.Constructor; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.ServletContext; +import javax.servlet.ServletRequest; + +import org.springframework.beans.BeanUtils; +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; +import org.springframework.util.ClassUtils; + +/** + * Default builder for {@link MockMultipartHttpServletRequest}. + * + * @author Rossen Stoyanchev + * @author Arjen Poutsma + * @since 3.2 + */ +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. + */ + MockMultipartHttpServletRequestBuilder(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 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 = ClassUtils.hasMethod(ServletRequest.class, "startAsync") ? + createServlet3Request() : new MockMultipartHttpServletRequest(); + + for (MockMultipartFile file : this.files) { + request.addFile(file); + } + return request; + } + + private static MockMultipartHttpServletRequest createServlet3Request() { + try { + String className = "org.springframework.test.web.mock.servlet.request.Servlet3MockMultipartHttpServletRequest"; + Class clazz = ClassUtils.forName(className, MockMultipartHttpServletRequestBuilder.class.getClassLoader()); + Constructor constructor = clazz.getConstructor(ServletContext.class); + return (MockMultipartHttpServletRequest) BeanUtils.instantiateClass(constructor); + } + catch (Throwable t) { + throw new IllegalStateException("Failed to instantiate MockHttpServletRequest", t); + } + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/MockMvcRequestBuilders.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/MockMvcRequestBuilders.java new file mode 100644 index 0000000000..daf0d64e40 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/MockMvcRequestBuilders.java @@ -0,0 +1,107 @@ +/* + * 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.mock.servlet.request; + +import java.net.URI; + +import org.springframework.http.HttpMethod; +import org.springframework.test.web.mock.servlet.RequestBuilder; +import org.springframework.web.util.UriTemplate; + +/** + * 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 URI template including any component (e.g. scheme, host, query) + * @param urlVariables zero or more URI variables + */ + public static MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables) { + return request(HttpMethod.GET, urlTemplate, urlVariables); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a POST request. + * + * @param urlTemplate a URI template including any component (e.g. scheme, host, query) + * @param urlVariables zero or more URI variables + */ + public static MockHttpServletRequestBuilder post(String urlTemplate, Object... urlVariables) { + return request(HttpMethod.POST, urlTemplate, urlVariables); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a PUT request. + * + * @param urlTemplate a URI template including any component (e.g. scheme, host, query) + * @param urlVariables zero or more URI variables + */ + public static MockHttpServletRequestBuilder put(String urlTemplate, Object... urlVariables) { + return request(HttpMethod.PUT, urlTemplate, urlVariables); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a DELETE request. + * + * @param urlTemplate a URI template including any component (e.g. scheme, host, query) + * @param urlVariables zero or more URI variables + */ + public static MockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVariables) { + return request(HttpMethod.DELETE, urlTemplate, urlVariables); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for a multipart request. + * + * @param urlTemplate a URI template including any component (e.g. scheme, host, query) + * @param urlVariables zero or more URI variables + */ + public static MockMultipartHttpServletRequestBuilder fileUpload(String urlTemplate, Object... urlVariables) { + URI url = expandUrl(urlTemplate, urlVariables); + return new MockMultipartHttpServletRequestBuilder(url); + } + + /** + * Create a {@link MockHttpServletRequestBuilder} for any HTTP method. + * + * @param httpMethod the HTTP method + * @param urlTemplate a URI template including any component (e.g. scheme, host, query) + * @param urlVariables zero or more URI variables + */ + private static MockHttpServletRequestBuilder request(HttpMethod httpMethod, String urlTemplate, Object... urlVariables) { + URI url = expandUrl(urlTemplate, urlVariables); + return new MockHttpServletRequestBuilder(url, httpMethod); + } + + private static URI expandUrl(String urlTemplate, Object[] urlVariables) { + UriTemplate uriTemplate = new UriTemplate(urlTemplate); + return uriTemplate.expand(urlVariables); + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/RequestPostProcessor.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/RequestPostProcessor.java new file mode 100644 index 0000000000..cd780b6d21 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/RequestPostProcessor.java @@ -0,0 +1,45 @@ +/* + * 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.mock.servlet.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/Servlet3MockHttpServletRequest.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/Servlet3MockHttpServletRequest.java new file mode 100644 index 0000000000..618cf93897 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/Servlet3MockHttpServletRequest.java @@ -0,0 +1,103 @@ +/* + * 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.mock.servlet.request; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Part; + +import org.springframework.mock.web.MockHttpServletRequest; + +/** + * A Servlet 3 sub-class of MockHttpServletRequest. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +class Servlet3MockHttpServletRequest extends MockHttpServletRequest { + + private boolean asyncStarted; + + private MockAsyncContext asyncContext; + + private Map parts = new HashMap(); + + + public Servlet3MockHttpServletRequest(ServletContext servletContext) { + super(servletContext); + } + + public boolean isAsyncSupported() { + return true; + } + + public AsyncContext startAsync() { + return startAsync(this, null); + } + + public AsyncContext startAsync(ServletRequest request, ServletResponse response) { + this.asyncStarted = true; + this.asyncContext = new MockAsyncContext(request, response); + return this.asyncContext; + } + + public AsyncContext getAsyncContext() { + return this.asyncContext; + } + + public void setAsyncContext(MockAsyncContext asyncContext) { + this.asyncContext = asyncContext; + } + + public DispatcherType getDispatcherType() { + return DispatcherType.REQUEST; + } + + public boolean isAsyncStarted() { + return this.asyncStarted; + } + + public void setAsyncStarted(boolean asyncStarted) { + this.asyncStarted = asyncStarted; + } + + public void addPart(Part part) { + this.parts.put(part.getName(), part); + } + + public Part getPart(String key) throws IOException, IllegalStateException, ServletException { + return this.parts.get(key); + } + + public Collection getParts() throws IOException, IllegalStateException, ServletException { + return this.parts.values(); + } + + public boolean authenticate(HttpServletResponse response) throws IOException, ServletException { + throw new UnsupportedOperationException(); + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/Servlet3MockMultipartHttpServletRequest.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/Servlet3MockMultipartHttpServletRequest.java new file mode 100644 index 0000000000..7d60aa4434 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/Servlet3MockMultipartHttpServletRequest.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.mock.servlet.request; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.AsyncContext; +import javax.servlet.DispatcherType; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Part; + +import org.springframework.mock.web.MockMultipartHttpServletRequest; + +/** + * A Servlet 3 sub-class of MockMultipartHttpServletRequest. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +class Servlet3MockMultipartHttpServletRequest extends MockMultipartHttpServletRequest { + + private boolean asyncStarted; + + private MockAsyncContext asyncContext; + + private Map parts = new HashMap(); + + + public boolean isAsyncSupported() { + return true; + } + + public AsyncContext startAsync() { + return startAsync(this, null); + } + + public AsyncContext startAsync(ServletRequest request, ServletResponse response) { + this.asyncStarted = true; + this.asyncContext = new MockAsyncContext(request, response); + return this.asyncContext; + } + + public AsyncContext getAsyncContext() { + return this.asyncContext; + } + + public void setAsyncContext(MockAsyncContext asyncContext) { + this.asyncContext = asyncContext; + } + + public DispatcherType getDispatcherType() { + return DispatcherType.REQUEST; + } + + public boolean isAsyncStarted() { + return this.asyncStarted; + } + + public void setAsyncStarted(boolean asyncStarted) { + this.asyncStarted = asyncStarted; + } + + public void addPart(Part part) { + this.parts.put(part.getName(), part); + } + + public Part getPart(String key) throws IOException, IllegalStateException, ServletException { + return this.parts.get(key); + } + + public Collection getParts() throws IOException, IllegalStateException, ServletException { + return this.parts.values(); + } + + public boolean authenticate(HttpServletResponse response) throws IOException, ServletException { + throw new UnsupportedOperationException(); + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/package-info.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/package-info.java new file mode 100644 index 0000000000..b1b203db0a --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/request/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * Contains built-in {@link org.springframework.test.web.mock.servlet.RequestBuilder} + * implementations. Use + * {@link org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders} + * to gain access to instances of those implementations. + */ +package org.springframework.test.web.mock.servlet.request; diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/ContentResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/ContentResultMatchers.java new file mode 100644 index 0000000000..29589662e8 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/ContentResultMatchers.java @@ -0,0 +1,175 @@ +/* + * 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.mock.servlet.result; + +import static org.springframework.test.web.mock.AssertionErrors.assertEquals; +import static org.springframework.test.web.mock.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.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.ResultMatcher; +import org.springframework.test.web.mock.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); + } + }; + } + + /** + * 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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/CookieResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/CookieResultMatchers.java new file mode 100644 index 0000000000..1c6b81225a --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/CookieResultMatchers.java @@ -0,0 +1,198 @@ +/* + * 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.mock.servlet.result; + +import static org.springframework.test.web.mock.AssertionErrors.assertEquals; +import static org.springframework.test.web.mock.AssertionErrors.assertTrue; + +import javax.servlet.http.Cookie; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.ResultMatcher; + +/** + * Factory for response cookie assertions. An instance of this class is + * typically accessed via {@link MockMvcResultMatchers#cookie()}. + * + * @author Rossen Stoyanchev + * @author Thomas Bruyelle + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/FlashAttributeResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/FlashAttributeResultMatchers.java new file mode 100644 index 0000000000..8408bb30fb --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/FlashAttributeResultMatchers.java @@ -0,0 +1,87 @@ +/* + * 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.mock.servlet.result; + +import static org.springframework.test.web.mock.AssertionErrors.*; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.ResultMatcher; + +/** + * Factory for "output" flash attribute assertions. An instance of this class is + * typically accessed via {@link MockMvcResultMatchers#flash()}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/HandlerResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/HandlerResultMatchers.java new file mode 100644 index 0000000000..f0f67c9530 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/HandlerResultMatchers.java @@ -0,0 +1,113 @@ +/* + * 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.mock.servlet.result; + +import static org.springframework.test.web.mock.AssertionErrors.assertEquals; +import static org.springframework.test.web.mock.AssertionErrors.assertTrue; + +import java.lang.reflect.Method; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/HeaderResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/HeaderResultMatchers.java new file mode 100644 index 0000000000..cb8f12801c --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/HeaderResultMatchers.java @@ -0,0 +1,73 @@ +/* + * 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.mock.servlet.result; + +import static org.springframework.test.web.mock.AssertionErrors.assertEquals; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.ResultMatcher; + +/** + * Factory for response header assertions. An instance of this + * class is usually accessed via {@link MockMvcResultMatchers#header()}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/JsonPathResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/JsonPathResultMatchers.java new file mode 100644 index 0000000000..be3929e599 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/JsonPathResultMatchers.java @@ -0,0 +1,101 @@ +/* + * 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.mock.servlet.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.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.ResultMatcher; +import org.springframework.test.web.mock.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/MockMvcResultHandlers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/MockMvcResultHandlers.java new file mode 100644 index 0000000000..572d9157f7 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/MockMvcResultHandlers.java @@ -0,0 +1,66 @@ +/* + * 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.mock.servlet.result; + +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/MockMvcResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/MockMvcResultMatchers.java new file mode 100644 index 0000000000..6d7a9fee19 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/MockMvcResultMatchers.java @@ -0,0 +1,185 @@ +/* + * 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.mock.servlet.result; + +import static org.springframework.test.web.mock.AssertionErrors.assertEquals; + +import java.util.Map; + +import javax.xml.xpath.XPathExpressionException; + +import org.hamcrest.Matcher; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/ModelResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/ModelResultMatchers.java new file mode 100644 index 0000000000..6938582dca --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/ModelResultMatchers.java @@ -0,0 +1,175 @@ +/* + * 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.mock.servlet.result; + +import static org.springframework.test.web.mock.AssertionErrors.assertEquals; +import static org.springframework.test.web.mock.AssertionErrors.assertTrue; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/PrintingResultHandler.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/PrintingResultHandler.java new file mode 100644 index 0000000000..9c21cfe5a8 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/PrintingResultHandler.java @@ -0,0 +1,200 @@ +/* + * 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.mock.servlet.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.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/RequestResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/RequestResultMatchers.java new file mode 100644 index 0000000000..e9a68a350b --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/RequestResultMatchers.java @@ -0,0 +1,153 @@ +/* + * 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.mock.servlet.result; + +import static org.hamcrest.Matchers.equalTo; + +import java.util.concurrent.Callable; + +import javax.servlet.http.HttpServletRequest; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.ResultMatcher; +import org.springframework.web.context.request.async.AsyncTask; +import org.springframework.web.context.request.async.DeferredResult; +import org.springframework.web.context.request.async.WebAsyncManager; +import org.springframework.web.context.request.async.WebAsyncUtils; + +/** + * Factory for assertions on the request. An instance of this class is + * typically accessed via {@link MockMvcResultMatchers#request()}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class RequestResultMatchers { + + + /** + * Protected constructor. + * Use {@link MockMvcResultMatchers#request()}. + */ + protected RequestResultMatchers() { + } + + /** + * Assert a request attribute value with the given Hamcrest {@link Matcher}. + * Whether asynchronous processing started, usually as a result of a + * controller method returning {@link Callable} or {@link DeferredResult}. + * The test will await the completion of a {@code Callable} so that + * {@link #asyncResult(Matcher)} can be used to assert the resulting value. + * Neither a {@code Callable} nor a {@code DeferredResult} will complete + * processing all the way since a {@link MockHttpServletRequest} does not + * perform asynchronous dispatches. + */ + public ResultMatcher asyncStarted() { + return new ResultMatcher() { + public void match(MvcResult result) { + HttpServletRequest request = result.getRequest(); + MatcherAssert.assertThat("Async started", request.isAsyncStarted(), equalTo(true)); + } + }; + } + + /** + * Assert that asynchronous processing was not start. + * @see #asyncStarted() + */ + public ResultMatcher asyncNotStarted() { + return new ResultMatcher() { + public void match(MvcResult result) { + HttpServletRequest request = result.getRequest(); + MatcherAssert.assertThat("Async started", request.isAsyncStarted(), equalTo(false)); + } + }; + } + + /** + * Assert the result from asynchronous processing with the given matcher. + * This method can be used when a controller method returns {@link Callable} + * or {@link AsyncTask}. The value matched is the value returned from the + * {@code Callable} or the exception raised. + */ + public ResultMatcher asyncResult(final Matcher matcher) { + return new ResultMatcher() { + @SuppressWarnings("unchecked") + public void match(MvcResult result) { + HttpServletRequest request = result.getRequest(); + WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); + MatcherAssert.assertThat("Async started", request.isAsyncStarted(), equalTo(true)); + MatcherAssert.assertThat("Callable result", (T) asyncManager.getConcurrentResult(), matcher); + } + }; + } + + /** + * Assert the result from asynchronous processing. + * This method can be used when a controller method returns {@link Callable} + * or {@link AsyncTask}. The value matched is the value returned from the + * {@code Callable} or the exception raised. + */ + public ResultMatcher asyncResult(Object expectedResult) { + return asyncResult(equalTo(expectedResult)); + } + + /** + * 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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/StatusResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/StatusResultMatchers.java new file mode 100644 index 0000000000..23f133896a --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/StatusResultMatchers.java @@ -0,0 +1,544 @@ +/* + * 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.mock.servlet.result; + +import static org.springframework.test.web.mock.AssertionErrors.assertEquals; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.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 + * @since 3.2 + */ +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). + * @deprecated matching the deprecation of HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE + */ + public ResultMatcher isInsufficientSpaceOnResource() { + return matcher(HttpStatus.INSUFFICIENT_SPACE_ON_RESOURCE); + } + + /** + * Assert the response status code is {@code HttpStatus.METHOD_FAILURE} (420). + * @deprecated matching the deprecation of HttpStatus.METHOD_FAILURE + */ + public ResultMatcher isMethodFailure() { + return matcher(HttpStatus.METHOD_FAILURE); + } + + /** + * Assert the response status code is {@code HttpStatus.DESTINATION_LOCKED} (421). + * @deprecated matching the deprecation of HttpStatus.DESTINATION_LOCKED + */ + 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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/ViewResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/ViewResultMatchers.java new file mode 100644 index 0000000000..76c095ad92 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/ViewResultMatchers.java @@ -0,0 +1,63 @@ +/* + * 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.mock.servlet.result; + +import static org.springframework.test.web.mock.AssertionErrors.assertTrue; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.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()}. + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/XpathResultMatchers.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/XpathResultMatchers.java new file mode 100644 index 0000000000..fa2e92ba9e --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/XpathResultMatchers.java @@ -0,0 +1,160 @@ +/* + * 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.mock.servlet.result; + +import java.util.Map; + +import javax.xml.xpath.XPathExpressionException; + +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.ResultMatcher; +import org.springframework.test.web.mock.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 + * @since 3.2 + */ +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/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/package-info.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/package-info.java new file mode 100644 index 0000000000..d57e56214a --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/result/package-info.java @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * Contains built-in {@code ResultMatcher} and {@code ResultHandler} implementations. + * Use {@link org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers} + * and {@link org.springframework.test.web.mock.servlet.result.MockMvcResultHandlers} + * to access to instances of those implementations. + */ +package org.springframework.test.web.mock.servlet.result; diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/DefaultMockMvcBuilder.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/DefaultMockMvcBuilder.java new file mode 100644 index 0000000000..8a7fede5b4 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/DefaultMockMvcBuilder.java @@ -0,0 +1,206 @@ +/* + * 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.mock.servlet.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.mock.servlet.MockMvc; +import org.springframework.test.web.mock.servlet.MockMvcBuilder; +import org.springframework.test.web.mock.servlet.MockMvcBuilderSupport; +import org.springframework.test.web.mock.servlet.RequestBuilder; +import org.springframework.test.web.mock.servlet.ResultHandler; +import org.springframework.test.web.mock.servlet.ResultMatcher; +import org.springframework.util.Assert; +import org.springframework.web.context.WebApplicationContext; + +/** + * An concrete implementation of {@link MockMvcBuilder} with methods for + * configuring filters, default request properties, and global expectations and + * result actions. + * + * @author Rossen Stoyanchev + * @author Rob Winch + * @since 3.2 + */ +public class DefaultMockMvcBuilder extends MockMvcBuilderSupport + implements MockMvcBuilder { + + private final WebApplicationContext webAppContext; + + private List filters = new ArrayList(); + + private RequestBuilder defaultRequestBuilder; + + private final List globalResultMatchers = new ArrayList(); + + private final List globalResultHandlers = new ArrayList(); + + + /** + * Protected constructor. Not intended for direct instantiation. + * @see MockMvcBuilders#webAppContextSetup(WebApplicationContext) + */ + protected DefaultMockMvcBuilder(WebApplicationContext webAppContext) { + Assert.notNull(webAppContext, "WebApplicationContext is required"); + Assert.notNull(webAppContext.getServletContext(), "WebApplicationContext must have a ServletContext"); + this.webAppContext = webAppContext; + } + + /** + * 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.mock.servlet.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.mock.servlet.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.mock.servlet.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() { + + initWebAppContext(this.webAppContext); + + ServletContext servletContext = this.webAppContext.getServletContext(); + MockServletConfig mockServletConfig = new MockServletConfig(servletContext); + + Filter[] filterArray = this.filters.toArray(new Filter[this.filters.size()]); + + return super.createMockMvc(filterArray, mockServletConfig, this.webAppContext, + this.defaultRequestBuilder, this.globalResultMatchers, this.globalResultHandlers); + } + + /** + * Invoked from {@link #build()} before the {@link MockMvc} instance is created. + * Allows sub-classes to further initialize the {@code WebApplicationContext} + * and the {@code javax.servlet.ServletContext} it contains. + */ + protected void initWebAppContext(WebApplicationContext webAppContext) { + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/MockMvcBuilders.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/MockMvcBuilders.java new file mode 100644 index 0000000000..b717acc6ad --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/MockMvcBuilders.java @@ -0,0 +1,74 @@ +/* + * 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.mock.servlet.setup; + +import javax.servlet.ServletContext; + +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.test.web.mock.servlet.MockMvcBuilder; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.DispatcherServlet; + +/** + * 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 + * @since 3.2 + */ +public class MockMvcBuilders { + + /** + * Build a {@link MockMvc} using the given, fully initialized, i.e. + * refreshed, {@link WebApplicationContext}. The {@link DispatcherServlet} + * will use the context to discover Spring MVC infrastructure and + * application controllers in it. The context must have been configured with + * a {@link ServletContext}. + */ + public static DefaultMockMvcBuilder> webAppContextSetup(WebApplicationContext context) { + return new DefaultMockMvcBuilder>(context); + } + + /** + * Build a {@link MockMvc} by registering one or more {@code @Controller}'s + * instances and configuring Spring MVC infrastructure programmatically. + * This allows full control over the instantiation and initialization of + * controllers, and their dependencies, similar to plain unit tests while + * also making it possible to test one controller at a time. + * + *

When this option is used, the minimum infrastructure required by the + * {@link DispatcherServlet} to serve requests with annotated controllers is + * automatically created, and can be customized, resulting in configuration + * that is equivalent to what the MVC Java configuration provides except + * using builder style methods. + * + *

If the Spring MVC configuration of an application is relatively + * straight-forward, for example when using the MVC namespace or the MVC + * Java config, then using this builder might be a good option for testing + * a majority of controllers. A much smaller number of tests can be used + * to focus on testing and verifying the actual Spring MVC configuration. + * + * @param controllers one or more {@link Controller @Controller}'s to test + */ + public static StandaloneMockMvcBuilder standaloneSetup(Object... controllers) { + return new StandaloneMockMvcBuilder(controllers); + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/PatternMappingFilterProxy.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/PatternMappingFilterProxy.java new file mode 100644 index 0000000000..85ca1ff089 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/PatternMappingFilterProxy.java @@ -0,0 +1,130 @@ +/* + * 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.mock.servlet.setup; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.util.Assert; +import org.springframework.web.util.UrlPathHelper; + +/** + * A Filter that invokes a delegate {@link Filter} only if the request URL + * matches the pattern it is mapped to using pattern matching as defined in the + * Servlet spec. + * + * @author Rob Winch + * @since 3.2 + */ +final class PatternMappingFilterProxy implements Filter { + + private static final String EXTENSION_MAPPING_PATTERN = "*."; + + private static final String PATH_MAPPING_PATTERN = "/*"; + + private static final UrlPathHelper urlPathHelper = new UrlPathHelper(); + + private final Filter delegate; + + /** Patterns that require an exact match, e.g. "/test" */ + private final List exactMatches = new ArrayList(); + + /** Patterns that require the URL to have a specific prefix, e.g. "/test/*" */ + private final List startsWithMatches = new ArrayList(); + + /** Patterns that require the request URL to have a specific suffix, e.g. "*.html" */ + private final List endsWithMatches = new ArrayList(); + + + /** + * Creates a new instance. + */ + public PatternMappingFilterProxy(Filter delegate, String... urlPatterns) { + Assert.notNull(delegate, "A delegate Filter is required"); + this.delegate = delegate; + for(String urlPattern : urlPatterns) { + addUrlPattern(urlPattern); + } + } + + private void addUrlPattern(String urlPattern) { + Assert.notNull(urlPattern, "Found null URL Pattern"); + if(urlPattern.startsWith(EXTENSION_MAPPING_PATTERN)) { + this.endsWithMatches.add(urlPattern.substring(1, urlPattern.length())); + } else if(urlPattern.equals(PATH_MAPPING_PATTERN)) { + this.startsWithMatches.add(""); + } + else if (urlPattern.endsWith(PATH_MAPPING_PATTERN)) { + this.startsWithMatches.add(urlPattern.substring(0, urlPattern.length() - 1)); + this.exactMatches.add(urlPattern.substring(0, urlPattern.length() - 2)); + } else { + if("".equals(urlPattern)) { + urlPattern = "/"; + } + this.exactMatches.add(urlPattern); + } + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + String requestPath = urlPathHelper.getPathWithinApplication(httpRequest); + + if(matches(requestPath)) { + this.delegate.doFilter(request, response, filterChain); + } else { + filterChain.doFilter(request, response); + } + } + + private boolean matches(String requestPath) { + for(String pattern : this.exactMatches) { + if(pattern.equals(requestPath)) { + return true; + } + } + if(!requestPath.startsWith("/")) { + return false; + } + for(String pattern : this.endsWithMatches) { + if(requestPath.endsWith(pattern)) { + return true; + } + } + for(String pattern : this.startsWithMatches) { + if(requestPath.startsWith(pattern)) { + return true; + } + } + return false; + } + + public void init(FilterConfig filterConfig) throws ServletException { + this.delegate.init(filterConfig); + } + + public void destroy() { + this.delegate.destroy(); + } + +} \ No newline at end of file diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/StandaloneMockMvcBuilder.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/StandaloneMockMvcBuilder.java new file mode 100644 index 0000000000..e899264026 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/StandaloneMockMvcBuilder.java @@ -0,0 +1,382 @@ +/* + * 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.mock.servlet.setup; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.format.support.FormattingConversionService; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.mock.web.MockServletContext; +import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; +import org.springframework.validation.Validator; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationObjectSupport; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.HandlerMethodReturnValueHandler; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.FlashMapManager; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.ViewResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; +import org.springframework.web.servlet.handler.MappedInterceptor; +import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import org.springframework.web.servlet.support.SessionFlashMapManager; +import org.springframework.web.servlet.theme.FixedThemeResolver; +import org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator; +import org.springframework.web.servlet.view.InternalResourceViewResolver; + +/** + * A MockMvcBuilder that accepts {@code @Controller} registrations thus allowing + * full control over the instantiation and the initialization of controllers and + * their dependencies similar to plain unit tests, and also making it possible + * to test one controller at a time. + * + *

This builder creates the minimum infrastructure required by the + * {@link DispatcherServlet} to serve requests with annotated controllers and + * also provides methods to customize it. The resulting configuration and + * customizations possible are equivalent to using the MVC Java config except + * using builder style methods. + * + *

To configure view resolution, either select a "fixed" view to use for every + * performed request (see {@link #setSingleView(View)}) or provide a list of + * {@code ViewResolver}'s, see {@link #setViewResolvers(ViewResolver...)}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class StandaloneMockMvcBuilder extends DefaultMockMvcBuilder { + + private final Object[] controllers; + + private List> messageConverters = new ArrayList>(); + + private List customArgumentResolvers = new ArrayList(); + + private List customReturnValueHandlers = new ArrayList(); + + private final List mappedInterceptors = new ArrayList(); + + private Validator validator = null; + + private FormattingConversionService conversionService = null; + + private List handlerExceptionResolvers = new ArrayList(); + + private List viewResolvers; + + private LocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); + + private FlashMapManager flashMapManager = null; + + private boolean useSuffixPatternMatch = true; + + private boolean useTrailingSlashPatternMatch = true; + + + /** + * Protected constructor. Not intended for direct instantiation. + * @see MockMvcBuilders#standaloneSetup(Object...) + */ + protected StandaloneMockMvcBuilder(Object... controllers) { + super(new StubWebApplicationContext(new MockServletContext())); + Assert.isTrue(!ObjectUtils.isEmpty(controllers), "At least one controller is required"); + this.controllers = controllers; + } + + /** + * Set the message converters to use in argument resolvers and in return value + * handlers, which support reading and/or writing to the body of the request + * and response. If no message converters are added to the list, a default + * list of converters is added instead. + */ + public StandaloneMockMvcBuilder setMessageConverters(HttpMessageConverter...messageConverters) { + this.messageConverters = Arrays.asList(messageConverters); + return this; + } + + /** + * Provide a custom {@link Validator} instead of the one created by default. + * The default implementation used, assuming JSR-303 is on the classpath, is + * {@link org.springframework.validation.beanvalidation.LocalValidatorFactoryBean}. + */ + public StandaloneMockMvcBuilder setValidator(Validator validator) { + this.validator = validator; + return this; + } + + /** + * Provide a conversion service with custom formatters and converters. + * If not set, a {@link DefaultFormattingConversionService} is used by default. + */ + public StandaloneMockMvcBuilder setConversionService(FormattingConversionService conversionService) { + this.conversionService = conversionService; + return this; + } + + /** + * Add interceptors mapped to all incoming requests. + */ + public StandaloneMockMvcBuilder addInterceptors(HandlerInterceptor... interceptors) { + addMappedInterceptors(null, interceptors); + return this; + } + + /** + * Add interceptors mapped to a set of path patterns. + */ + public StandaloneMockMvcBuilder addMappedInterceptors(String[] pathPatterns, HandlerInterceptor... interceptors) { + for (HandlerInterceptor interceptor : interceptors) { + this.mappedInterceptors.add(new MappedInterceptor(pathPatterns, interceptor)); + } + return this; + } + + /** + * Provide custom resolvers for controller method arguments. + */ + public StandaloneMockMvcBuilder setCustomArgumentResolvers(HandlerMethodArgumentResolver... argumentResolvers) { + this.customArgumentResolvers = Arrays.asList(argumentResolvers); + return this; + } + + /** + * Provide custom handlers for controller method return values. + */ + public StandaloneMockMvcBuilder setCustomReturnValueHandlers(HandlerMethodReturnValueHandler... handlers) { + this.customReturnValueHandlers = Arrays.asList(handlers); + return this; + } + + + /** + * Set the HandlerExceptionResolver types to use. + */ + public void setHandlerExceptionResolvers(List exceptionResolvers) { + this.handlerExceptionResolvers = exceptionResolvers; + } + + /** + * Set up view resolution with the given {@link ViewResolver}s. + * If not set, an {@link InternalResourceViewResolver} is used by default. + */ + public StandaloneMockMvcBuilder setViewResolvers(ViewResolver...resolvers) { + this.viewResolvers = Arrays.asList(resolvers); + return this; + } + + /** + * Sets up a single {@link ViewResolver} that always returns the provided + * view instance. This is a convenient shortcut if you need to use one + * View instance only -- e.g. rendering generated content (JSON, XML, Atom). + */ + public StandaloneMockMvcBuilder setSingleView(View view) { + this.viewResolvers = Collections.singletonList(new StaticViewResolver(view)); + return this; + } + + /** + * Provide a LocaleResolver instance. + * If not provided, the default one used is {@link AcceptHeaderLocaleResolver}. + */ + public StandaloneMockMvcBuilder setLocaleResolver(LocaleResolver localeResolver) { + this.localeResolver = localeResolver; + return this; + } + + /** + * Provide a custom FlashMapManager instance. + * If not provided, {@code SessionFlashMapManager} is used by default. + */ + public StandaloneMockMvcBuilder setFlashMapManager(FlashMapManager flashMapManager) { + this.flashMapManager = flashMapManager; + return this; + } + + /** + * Whether to use suffix pattern match (".*") when matching patterns to + * requests. If enabled a method mapped to "/users" also matches to "/users.*". + *

The default value is {@code true}. + */ + public StandaloneMockMvcBuilder setUseSuffixPatternMatch(boolean useSuffixPatternMatch) { + this.useSuffixPatternMatch = useSuffixPatternMatch; + return this; + } + + /** + * Whether to match to URLs irrespective of the presence of a trailing slash. + * If enabled a method mapped to "/users" also matches to "/users/". + *

The default value is {@code true}. + */ + public StandaloneMockMvcBuilder setUseTrailingSlashPatternMatch(boolean useTrailingSlashPatternMatch) { + this.useTrailingSlashPatternMatch = useTrailingSlashPatternMatch; + return this; + } + + protected void initWebAppContext(WebApplicationContext cxt) { + StubWebApplicationContext mockCxt = (StubWebApplicationContext) cxt; + registerMvcSingletons(mockCxt); + cxt.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, mockCxt); + } + + private void registerMvcSingletons(StubWebApplicationContext cxt) { + + StandaloneConfiguration configuration = new StandaloneConfiguration(); + + RequestMappingHandlerMapping handlerMapping = configuration.requestMappingHandlerMapping(); + handlerMapping.setServletContext(cxt.getServletContext()); + handlerMapping.setApplicationContext(cxt); + cxt.addBean("requestMappingHandlerMapping", handlerMapping); + + RequestMappingHandlerAdapter handlerAdapter = configuration.requestMappingHandlerAdapter(); + handlerAdapter.setServletContext(cxt.getServletContext()); + handlerAdapter.setApplicationContext(cxt); + handlerAdapter.afterPropertiesSet(); + cxt.addBean("requestMappingHandlerAdapter", handlerAdapter); + + cxt.addBean("handlerExceptionResolver", configuration.handlerExceptionResolver()); + + cxt.addBeans(initViewResolvers(cxt)); + cxt.addBean(DispatcherServlet.LOCALE_RESOLVER_BEAN_NAME, this.localeResolver); + cxt.addBean(DispatcherServlet.THEME_RESOLVER_BEAN_NAME, new FixedThemeResolver()); + cxt.addBean(DispatcherServlet.REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, new DefaultRequestToViewNameTranslator()); + + this.flashMapManager = new SessionFlashMapManager(); + cxt.addBean(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, this.flashMapManager); + } + + private List initViewResolvers(WebApplicationContext wac) { + + this.viewResolvers = (this.viewResolvers == null) ? + Arrays.asList(new InternalResourceViewResolver()) : this.viewResolvers; + + for (Object viewResolver : this.viewResolvers) { + if (viewResolver instanceof WebApplicationObjectSupport) { + ((WebApplicationObjectSupport) viewResolver).setApplicationContext(wac); + } + } + + return this.viewResolvers; + } + + + /** Using the MVC Java configuration as the starting point for the "standalone" setup */ + private class StandaloneConfiguration extends WebMvcConfigurationSupport { + + @Override + public RequestMappingHandlerMapping requestMappingHandlerMapping() { + + StaticRequestMappingHandlerMapping handlerMapping = new StaticRequestMappingHandlerMapping(); + handlerMapping.registerHandlers(controllers); + + handlerMapping.setUseSuffixPatternMatch(useSuffixPatternMatch); + handlerMapping.setUseTrailingSlashMatch(useTrailingSlashPatternMatch); + handlerMapping.setOrder(0); + handlerMapping.setInterceptors(getInterceptors()); + + return handlerMapping; + } + + @Override + protected void configureMessageConverters(List> converters) { + converters.addAll(messageConverters); + } + + @Override + protected void addArgumentResolvers(List argumentResolvers) { + argumentResolvers.addAll(customArgumentResolvers); + } + + @Override + protected void addReturnValueHandlers(List returnValueHandlers) { + returnValueHandlers.addAll(customReturnValueHandlers); + } + + @Override + protected void addInterceptors(InterceptorRegistry registry) { + for (MappedInterceptor interceptor : mappedInterceptors) { + InterceptorRegistration registration = registry.addInterceptor(interceptor.getInterceptor()); + if (interceptor.getPathPatterns() != null) { + registration.addPathPatterns(interceptor.getPathPatterns()); + } + } + } + + @Override + public FormattingConversionService mvcConversionService() { + return (conversionService != null) ? conversionService : super.mvcConversionService(); + } + + @Override + public Validator mvcValidator() { + Validator mvcValidator = (validator != null) ? validator : super.mvcValidator(); + if (mvcValidator instanceof InitializingBean) { + try { + ((InitializingBean) mvcValidator).afterPropertiesSet(); + } + catch (Exception e) { + throw new BeanInitializationException("Failed to initialize Validator", e); + } + } + return mvcValidator; + } + + @Override + protected void configureHandlerExceptionResolvers(List exceptionResolvers) { + exceptionResolvers.addAll(StandaloneMockMvcBuilder.this.handlerExceptionResolvers); + } + } + + /** A {@code RequestMappingHandlerMapping} that allows registration of controllers */ + private static class StaticRequestMappingHandlerMapping extends RequestMappingHandlerMapping { + + public void registerHandlers(Object...handlers) { + for (Object handler : handlers) { + super.detectHandlerMethods(handler); + } + } + } + + /** A {@link ViewResolver} that always returns same View */ + private static class StaticViewResolver implements ViewResolver { + + private final View view; + + public StaticViewResolver(View view) { + this.view = view; + } + + public View resolveViewName(String viewName, Locale locale) throws Exception { + return this.view; + } + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/StubWebApplicationContext.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/StubWebApplicationContext.java new file mode 100644 index 0000000000..9f58a6032e --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/StubWebApplicationContext.java @@ -0,0 +1,343 @@ +/* + * 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.mock.servlet.setup; + +import java.io.IOException; +import java.lang.annotation.Annotation; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletContext; + +import org.springframework.beans.BeansException; +import org.springframework.beans.TypeConverter; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.beans.factory.config.DependencyDescriptor; +import org.springframework.beans.factory.support.StaticListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceResolvable; +import org.springframework.context.NoSuchMessageException; +import org.springframework.context.support.DelegatingMessageSource; +import org.springframework.core.env.Environment; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.util.ObjectUtils; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.ServletContextResourcePatternResolver; + +/** + * A mock WebApplicationContext that accepts registrations of object instances. + * + *

As registered object instances are instantiated and initialized + * externally, there is no wiring, bean initialization, lifecycle events, as + * well as no pre-processing and post-processing hooks typically associated with + * beans managed by an {@link ApplicationContext}. Just a simple lookup into a + * {@link StaticListableBeanFactory}. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +class StubWebApplicationContext implements WebApplicationContext { + + private final ServletContext servletContext; + + private final StubBeanFactory beanFactory = new StubBeanFactory(); + + private final String id = ObjectUtils.identityToString(this); + + private final String displayName = ObjectUtils.identityToString(this); + + private final long startupDate = System.currentTimeMillis(); + + private final Environment environment = new StandardEnvironment(); + + private final MessageSource messageSource = new DelegatingMessageSource(); + + private final ResourcePatternResolver resourcePatternResolver; + + + /** + * Class constructor. + */ + public StubWebApplicationContext(ServletContext servletContext) { + this.servletContext = servletContext; + this.resourcePatternResolver = new ServletContextResourcePatternResolver(servletContext); + } + + /** + * Returns an instance that can initialize {@link ApplicationContextAware} beans. + */ + public AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException { + return this.beanFactory; + } + + public ServletContext getServletContext() { + return this.servletContext; + } + + //--------------------------------------------------------------------- + // Implementation of ApplicationContext interface + //--------------------------------------------------------------------- + + public String getId() { + return this.id; + } + + public String getApplicationName() { + return ""; + } + + public String getDisplayName() { + return this.displayName; + } + + public long getStartupDate() { + return this.startupDate; + } + + public ApplicationContext getParent() { + return null; + } + + public Environment getEnvironment() { + return this.environment ; + } + + public void addBean(String name, Object bean) { + this.beanFactory.addBean(name, bean); + } + + public void addBeans(List beans) { + for (Object bean : beans) { + String name = bean.getClass().getName() + "#" + ObjectUtils.getIdentityHexString(bean); + this.beanFactory.addBean(name, bean); + } + } + + //--------------------------------------------------------------------- + // Implementation of BeanFactory interface + //--------------------------------------------------------------------- + + public Object getBean(String name) throws BeansException { + return this.beanFactory.getBean(name); + } + + public T getBean(String name, Class requiredType) throws BeansException { + return this.beanFactory.getBean(name, requiredType); + } + + public T getBean(Class requiredType) throws BeansException { + return this.beanFactory.getBean(requiredType); + } + + public Object getBean(String name, Object... args) throws BeansException { + return this.beanFactory.getBean(name, args); + } + + public boolean containsBean(String name) { + return this.beanFactory.containsBean(name); + } + + public boolean isSingleton(String name) throws NoSuchBeanDefinitionException { + return this.beanFactory.isSingleton(name); + } + + public boolean isPrototype(String name) throws NoSuchBeanDefinitionException { + return this.beanFactory.isPrototype(name); + } + + public boolean isTypeMatch(String name, Class targetType) throws NoSuchBeanDefinitionException { + return this.beanFactory.isTypeMatch(name, targetType); + } + + public Class getType(String name) throws NoSuchBeanDefinitionException { + return this.beanFactory.getType(name); + } + + public String[] getAliases(String name) { + return this.beanFactory.getAliases(name); + } + + //--------------------------------------------------------------------- + // Implementation of ListableBeanFactory interface + //--------------------------------------------------------------------- + + public boolean containsBeanDefinition(String beanName) { + return this.beanFactory.containsBeanDefinition(beanName); + } + + public int getBeanDefinitionCount() { + return this.beanFactory.getBeanDefinitionCount(); + } + + public String[] getBeanDefinitionNames() { + return this.beanFactory.getBeanDefinitionNames(); + } + + public String[] getBeanNamesForType(Class type) { + return this.beanFactory.getBeanNamesForType(type); + } + + public String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit) { + return this.beanFactory.getBeanNamesForType(type, includeNonSingletons, allowEagerInit); + } + + public Map getBeansOfType(Class type) throws BeansException { + return this.beanFactory.getBeansOfType(type); + } + + public Map getBeansOfType(Class type, boolean includeNonSingletons, boolean allowEagerInit) + throws BeansException { + + return this.beanFactory.getBeansOfType(type, includeNonSingletons, allowEagerInit); + } + + public Map getBeansWithAnnotation(Class annotationType) + throws BeansException { + + return this.beanFactory.getBeansWithAnnotation(annotationType); + } + + public A findAnnotationOnBean(String beanName, Class annotationType) { + return this.beanFactory.findAnnotationOnBean(beanName, annotationType); + } + + //--------------------------------------------------------------------- + // Implementation of HierarchicalBeanFactory interface + //--------------------------------------------------------------------- + + public BeanFactory getParentBeanFactory() { + return null; + } + + public boolean containsLocalBean(String name) { + return this.beanFactory.containsBean(name); + } + + //--------------------------------------------------------------------- + // Implementation of MessageSource interface + //--------------------------------------------------------------------- + + public String getMessage(String code, Object args[], String defaultMessage, Locale locale) { + return this.messageSource.getMessage(code, args, defaultMessage, locale); + } + + public String getMessage(String code, Object args[], Locale locale) throws NoSuchMessageException { + return this.messageSource.getMessage(code, args, locale); + } + + public String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException { + return this.messageSource.getMessage(resolvable, locale); + } + + //--------------------------------------------------------------------- + // Implementation of ResourceLoader interface + //--------------------------------------------------------------------- + + public ClassLoader getClassLoader() { + return null; + } + + public Resource getResource(String location) { + return this.resourcePatternResolver.getResource(location); + } + + //--------------------------------------------------------------------- + // Other + //--------------------------------------------------------------------- + + public void publishEvent(ApplicationEvent event) { + } + + public Resource[] getResources(String locationPattern) throws IOException { + return this.resourcePatternResolver.getResources(locationPattern); + } + + + /** + * An extension of StaticListableBeanFactory that implements + * AutowireCapableBeanFactory in order to allow bean initialization of + * {@link ApplicationContextAware} singletons. + */ + private class StubBeanFactory extends StaticListableBeanFactory implements AutowireCapableBeanFactory { + + public Object initializeBean(Object existingBean, String beanName) throws BeansException { + if (existingBean instanceof ApplicationContextAware) { + ((ApplicationContextAware) existingBean).setApplicationContext(StubWebApplicationContext.this); + } + return existingBean; + } + + public T createBean(Class beanClass) throws BeansException { + throw new UnsupportedOperationException("Bean creation is not supported"); + } + + @SuppressWarnings("rawtypes") + public Object createBean(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException { + throw new UnsupportedOperationException("Bean creation is not supported"); + } + + @SuppressWarnings("rawtypes") + public Object autowire(Class beanClass, int autowireMode, boolean dependencyCheck) throws BeansException { + return null; + } + + public void autowireBean(Object existingBean) throws BeansException { + throw new UnsupportedOperationException("Autowiring is not supported"); + } + + public void autowireBeanProperties(Object existingBean, int autowireMode, boolean dependencyCheck) throws BeansException { + throw new UnsupportedOperationException("Autowiring is not supported"); + } + + public Object configureBean(Object existingBean, String beanName) throws BeansException { + throw new UnsupportedOperationException("Configuring a bean is not supported"); + } + + public Object resolveDependency(DependencyDescriptor descriptor, String beanName) throws BeansException { + throw new UnsupportedOperationException("Dependency resolution is not supported"); + } + + public Object resolveDependency(DependencyDescriptor descriptor, String beanName, Set autowiredBeanNames, + TypeConverter typeConverter) throws BeansException { + throw new UnsupportedOperationException("Dependency resolution is not supported"); + } + + public void applyBeanPropertyValues(Object existingBean, String beanName) throws BeansException { + throw new UnsupportedOperationException("Bean property initialization is not supported"); + } + + public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName) + throws BeansException { + throw new UnsupportedOperationException("Post processing is not supported"); + } + + public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) + throws BeansException { + throw new UnsupportedOperationException("Post processing is not supported"); + } + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/package-info.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/package-info.java new file mode 100644 index 0000000000..0050912470 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/servlet/setup/package-info.java @@ -0,0 +1,22 @@ +/* + * 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. + */ + +/** + * Contains built-in {@code MockMvcBuilder} implementations. + * Use {@link org.springframework.test.web.mock.servlet.setup.MockMvcBuilders} + * to access to instances of those implementations. + */ +package org.springframework.test.web.mock.servlet.setup; diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/support/JsonPathExpectationsHelper.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/support/JsonPathExpectationsHelper.java new file mode 100644 index 0000000000..60cf32a70d --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/support/JsonPathExpectationsHelper.java @@ -0,0 +1,122 @@ +/* + * 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.mock.support; + +import static org.springframework.test.web.mock.AssertionErrors.assertTrue; + +import java.text.ParseException; +import java.util.List; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; + +import com.jayway.jsonpath.InvalidPathException; +import com.jayway.jsonpath.JsonPath; + +/** + * A helper class for applying assertions via JSONPath expressions. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class JsonPathExpectationsHelper { + + private final String expression; + + private final JsonPath jsonPath; + + + /** + * Class constructor. + * + * @param expression the JSONPath expression + * @param args arguments to parameterize the JSONPath expression with using the + * formatting specifiers defined in {@link String#format(String, Object...)} + */ + public JsonPathExpectationsHelper(String expression, Object ... args) { + this.expression = String.format(expression, args); + this.jsonPath = JsonPath.compile(this.expression); + } + + /** + * Evaluate the JSONPath and assert the resulting value with the given {@code Matcher}. + */ + @SuppressWarnings("unchecked") + public void assertValue(String content, Matcher matcher) throws ParseException { + T value = (T) evaluateJsonPath(content); + MatcherAssert.assertThat("JSON path: " + this.expression, value, matcher); + } + + private Object evaluateJsonPath(String content) throws ParseException { + String message = "No value for JSON path: " + this.expression + ", exception: "; + try { + return this.jsonPath.read(content); + } + catch (InvalidPathException ex) { + throw new AssertionError(message + ex.getMessage()); + } + catch (ArrayIndexOutOfBoundsException ex) { + throw new AssertionError(message + ex.getMessage()); + } + catch (IndexOutOfBoundsException ex) { + throw new AssertionError(message + ex.getMessage()); + } + } + + /** + * Apply the JSONPath and assert the resulting value. + */ + public void assertValue(Object value) throws ParseException { + assertValue(Matchers.equalTo(value)); + } + + /** + * Evaluate the JSON path and assert the resulting content exists. + */ + public void exists(String content) throws ParseException { + Object value = evaluateJsonPath(content); + String reason = "No value for JSON path: " + this.expression; + assertTrue(reason, value != null); + if (List.class.isInstance(value)) { + assertTrue(reason, !((List) value).isEmpty()); + } + } + + /** + * Evaluate the JSON path and assert it doesn't point to any content. + */ + public void doesNotExist(String content) throws ParseException { + + Object value; + try { + value = evaluateJsonPath(content); + } + catch (AssertionError ex) { + return; + } + + String reason = String.format("Expected no value for JSON path: %s but found: %s", this.expression, value); + if (List.class.isInstance(value)) { + assertTrue(reason, ((List) value).isEmpty()); + } + else { + assertTrue(reason, value == null); + } + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/support/XmlExpectationsHelper.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/support/XmlExpectationsHelper.java new file mode 100644 index 0000000000..578a2a6a5c --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/support/XmlExpectationsHelper.java @@ -0,0 +1,96 @@ +/* + * 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.mock.support; + +import java.io.StringReader; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.Source; +import javax.xml.transform.dom.DOMSource; + +import org.custommonkey.xmlunit.Diff; +import org.custommonkey.xmlunit.XMLUnit; +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.springframework.test.web.mock.AssertionErrors; +import org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; + +/** + * A helper class for assertions on XML content. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class XmlExpectationsHelper { + + + /** + * Parse the content as {@link Node} and apply a {@link Matcher}. + * @see org.hamcrest.Matchers#hasXPath + */ + public void assertNode(String content, Matcher matcher) throws Exception { + Document document = parseXmlString(content); + MatcherAssert.assertThat("Body content", document, matcher); + } + + private Document parseXmlString(String xml) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder documentBuilder = factory.newDocumentBuilder(); + InputSource inputSource = new InputSource(new StringReader(xml)); + Document document = documentBuilder.parse(inputSource); + return document; + } + + /** + * Parse the content as {@link DOMSource} and apply a {@link Matcher}. + * @see xml-matchers + */ + public void assertSource(String content, Matcher matcher) throws Exception { + Document document = parseXmlString(content); + MatcherAssert.assertThat("Body content", new DOMSource(document), matcher); + } + + /** + * Parse the expected and actual content strings as XML and assert that the + * two are "similar" -- i.e. they contain the same elements and attributes + * regardless of order. + * + *

Use of this method assumes the + * XMLUnit library is available. + * + * @param expected the expected XML content + * @param actual the actual XML content + * + * @see MockMvcResultMatchers#xpath(String, Object...) + * @see MockMvcResultMatchers#xpath(String, Map, Object...) + */ + public void assertXmlEqual(String expected, String actual) throws Exception { + Document control = XMLUnit.buildControlDocument(expected); + Document test = XMLUnit.buildTestDocument(actual); + Diff diff = new Diff(control, test); + if (!diff.similar()) { + AssertionErrors.fail("Body content " + diff.toString()); + } + } + +} diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/support/XpathExpectationsHelper.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/support/XpathExpectationsHelper.java new file mode 100644 index 0000000000..9a243adfb3 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/support/XpathExpectationsHelper.java @@ -0,0 +1,213 @@ +/* + * 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.mock.support; + +import static org.springframework.test.web.mock.AssertionErrors.assertEquals; + +import java.io.StringReader; +import java.util.Collections; +import java.util.Map; + +import javax.xml.namespace.QName; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpression; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.hamcrest.Matcher; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.springframework.util.xml.SimpleNamespaceContext; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; + +/** + * A helper class for applying assertions via XPath expressions. + * + * @author Rossen Stoyanchev + * @since 3.2 + */ +public class XpathExpectationsHelper { + + private final String expression; + + private final XPathExpression xpathExpression; + + + /** + * Class constructor. + * + * @param expression the XPath expression + * @param namespaces XML namespaces referenced in the XPath expression, or {@code null} + * @param args arguments to parameterize the XPath expression with using the + * formatting specifiers defined in {@link String#format(String, Object...)} + * @throws XPathExpressionException + */ + public XpathExpectationsHelper(String expression, Map namespaces, Object... args) + throws XPathExpressionException { + + this.expression = String.format(expression, args); + this.xpathExpression = compileXpathExpression(this.expression, namespaces); + } + + private XPathExpression compileXpathExpression(String expression, Map namespaces) + throws XPathExpressionException { + + SimpleNamespaceContext namespaceContext = new SimpleNamespaceContext(); + namespaceContext.setBindings((namespaces != null) ? namespaces : Collections. emptyMap()); + XPath xpath = XPathFactory.newInstance().newXPath(); + xpath.setNamespaceContext(namespaceContext); + return xpath.compile(expression); + } + + /** + * @return the compiled XPath expression. + */ + protected XPathExpression getXpathExpression() { + return this.xpathExpression; + } + + /** + * Parse the content, evaluate the XPath expression as a {@link Node}, and + * assert it with the given {@code Matcher}. + */ + public void assertNode(String content, final Matcher matcher) throws Exception { + Document document = parseXmlString(content); + Node node = evaluateXpath(document, XPathConstants.NODE, Node.class); + MatcherAssert.assertThat("Xpath: " + XpathExpectationsHelper.this.expression, node, matcher); + } + + /** + * Parse the given XML content to a {@link Document}. + * + * @param xml the content to parse + * @return the parsed document + * @throws Exception in case of errors + */ + protected Document parseXmlString(String xml) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + DocumentBuilder documentBuilder = factory.newDocumentBuilder(); + InputSource inputSource = new InputSource(new StringReader(xml)); + Document document = documentBuilder.parse(inputSource); + return document; + } + + /** + * Apply the XPath expression to given document. + * @throws XPathExpressionException + */ + @SuppressWarnings("unchecked") + protected T evaluateXpath(Document document, QName evaluationType, Class expectedClass) + throws XPathExpressionException { + + return (T) getXpathExpression().evaluate(document, evaluationType); + } + + /** + * Apply the XPath expression and assert the resulting content exists. + * @throws Exception if content parsing or expression evaluation fails + */ + public void exists(String content) throws Exception { + assertNode(content, Matchers.notNullValue()); + } + + /** + * Apply the XPath expression and assert the resulting content does not exist. + * @throws Exception if content parsing or expression evaluation fails + */ + public void doesNotExist(String content) throws Exception { + assertNode(content, Matchers.nullValue()); + } + + /** + * Apply the XPath expression and assert the resulting content with the + * given Hamcrest matcher. + * + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertNodeCount(String content, Matcher matcher) throws Exception { + Document document = parseXmlString(content); + NodeList nodeList = evaluateXpath(document, XPathConstants.NODESET, NodeList.class); + String reason = "nodeCount Xpath: " + XpathExpectationsHelper.this.expression; + MatcherAssert.assertThat(reason, nodeList.getLength(), matcher); + } + + /** + * Apply the XPath expression and assert the resulting content as an integer. + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertNodeCount(String content, int expectedCount) throws Exception { + assertNodeCount(content, Matchers.equalTo(expectedCount)); + } + + /** + * Apply the XPath expression and assert the resulting content with the + * given Hamcrest matcher. + * + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertString(String content, Matcher matcher) throws Exception { + Document document = parseXmlString(content); + String result = evaluateXpath(document, XPathConstants.STRING, String.class); + MatcherAssert.assertThat("Xpath: " + XpathExpectationsHelper.this.expression, result, matcher); + } + + /** + * Apply the XPath expression and assert the resulting content as a String. + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertString(String content, String expectedValue) throws Exception { + assertString(content, Matchers.equalTo(expectedValue)); + } + + /** + * Apply the XPath expression and assert the resulting content with the + * given Hamcrest matcher. + * + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertNumber(String content, Matcher matcher) throws Exception { + Document document = parseXmlString(content); + Double result = evaluateXpath(document, XPathConstants.NUMBER, Double.class); + MatcherAssert.assertThat("Xpath: " + XpathExpectationsHelper.this.expression, result, matcher); + } + + /** + * Apply the XPath expression and assert the resulting content as a Double. + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertNumber(String content, Double expectedValue) throws Exception { + assertNumber(content, Matchers.equalTo(expectedValue)); + } + + /** + * Apply the XPath expression and assert the resulting content as a Boolean. + * @throws Exception if content parsing or expression evaluation fails + */ + public void assertBoolean(String content, boolean expectedValue) throws Exception { + Document document = parseXmlString(content); + String result = evaluateXpath(document, XPathConstants.STRING, String.class); + assertEquals("Xpath:", expectedValue, Boolean.parseBoolean(result)); + } + +} \ No newline at end of file diff --git a/spring-test-mvc/src/main/java/org/springframework/test/web/mock/support/package-info.java b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/support/package-info.java new file mode 100644 index 0000000000..e778adb754 --- /dev/null +++ b/spring-test-mvc/src/main/java/org/springframework/test/web/mock/support/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Support classes shared among client and server-side Spring MVC Test classes. + */ +package org.springframework.test.web.mock.support; diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/Person.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/Person.java new file mode 100644 index 0000000000..404e7710fb --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/Person.java @@ -0,0 +1,88 @@ +/* + * 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.mock; + +import javax.validation.constraints.NotNull; +import javax.xml.bind.annotation.XmlRootElement; + +import org.springframework.util.ObjectUtils; + +@XmlRootElement +public class Person { + + @NotNull + private String name; + + private double someDouble; + + private boolean someBoolean; + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public Person setName(String name) { + this.name = name; + return this; + } + + public double getSomeDouble() { + return someDouble; + } + + public Person setSomeDouble(double someDouble) { + this.someDouble = someDouble; + return this; + } + + public boolean isSomeBoolean() { + return someBoolean; + } + + public Person setSomeBoolean(boolean someBoolean) { + this.someBoolean = someBoolean; + return this; + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof Person)) { + return false; + } + Person otherPerson = (Person) other; + return (ObjectUtils.nullSafeEquals(this.name, otherPerson.name) && + ObjectUtils.nullSafeEquals(this.someDouble, otherPerson.someDouble) && + ObjectUtils.nullSafeEquals(this.someBoolean, otherPerson.someBoolean)); + } + + @Override + public String toString() { + return "Person [name=" + this.name + ", someDouble=" + this.someDouble + + ", someBoolean=" + this.someBoolean + "]"; + } + +} \ No newline at end of file diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/MockClientHttpRequestFactoryTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/MockClientHttpRequestFactoryTests.java new file mode 100644 index 0000000000..de7c987480 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/MockClientHttpRequestFactoryTests.java @@ -0,0 +1,102 @@ +/* + * 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.mock.client; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.mock.client.match.RequestMatchers.anything; + +import java.net.URI; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpMethod; +import org.springframework.http.client.ClientHttpRequest; +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.test.web.mock.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +/** + * Tests for {@link MockClientHttpRequestFactory}. + * + * @author Rossen Stoyanchev + */ +public class MockClientHttpRequestFactoryTests { + + private MockRestServiceServer server; + + private ClientHttpRequestFactory factory; + + + @Before + public void setup() { + RestTemplate restTemplate = new RestTemplate(); + this.server = MockRestServiceServer.createServer(restTemplate); + this.factory = restTemplate.getRequestFactory(); + } + + @Test + public void createRequest() throws Exception { + URI uri = new URI("/foo"); + ClientHttpRequest expected = (ClientHttpRequest) this.server.expect(anything()); + ClientHttpRequest actual = this.factory.createRequest(uri, HttpMethod.GET); + + assertSame(expected, actual); + assertEquals(uri, actual.getURI()); + assertEquals(HttpMethod.GET, actual.getMethod()); + } + + @Test + public void noFurtherRequestsExpected() throws Exception { + try { + this.factory.createRequest(new URI("/foo"), HttpMethod.GET); + } + catch (AssertionError error) { + assertEquals("No further requests expected", error.getMessage()); + } + } + + @Test + public void verifyZeroExpected() throws Exception { + this.server.verify(); + } + + @Test + public void verifyExpectedEqualExecuted() throws Exception { + this.server.expect(anything()); + this.server.expect(anything()); + + this.factory.createRequest(new URI("/foo"), HttpMethod.GET); + this.factory.createRequest(new URI("/bar"), HttpMethod.POST); + } + + @Test + public void verifyMoreExpected() throws Exception { + this.server.expect(anything()); + this.server.expect(anything()); + + this.factory.createRequest(new URI("/foo"), HttpMethod.GET); + + try { + this.server.verify(); + } + catch (AssertionError error) { + assertTrue(error.getMessage(), error.getMessage().contains("1 out of 2 were executed")); + } + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/match/ContentRequestMatchersTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/match/ContentRequestMatchersTests.java new file mode 100644 index 0000000000..149ed43965 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/match/ContentRequestMatchersTests.java @@ -0,0 +1,123 @@ +/* + * 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.mock.client.match; + +import static org.hamcrest.Matchers.hasXPath; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.mock.http.client.MockClientHttpRequest; +import org.springframework.test.web.mock.client.match.ContentRequestMatchers; +import org.springframework.test.web.mock.client.match.RequestMatchers; + +/** + * Tests for {@link ContentRequestMatchers}. + * + * @author Rossen Stoyanchev + */ +public class ContentRequestMatchersTests { + + private MockClientHttpRequest request; + + @Before + public void setUp() { + this.request = new MockClientHttpRequest(); + } + + @Test + public void testContentType() throws Exception { + this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON); + + RequestMatchers.content().mimeType("application/json").match(this.request); + RequestMatchers.content().mimeType(MediaType.APPLICATION_JSON).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testContentTypeNoMatch1() throws Exception { + this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON); + + RequestMatchers.content().mimeType("application/xml").match(this.request); + } + + @Test(expected=AssertionError.class) + public void testContentTypeNoMatch2() throws Exception { + this.request.getHeaders().setContentType(MediaType.APPLICATION_JSON); + + RequestMatchers.content().mimeType(MediaType.APPLICATION_ATOM_XML).match(this.request); + } + + @Test + public void testString() throws Exception { + this.request.getBody().write("test".getBytes()); + + RequestMatchers.content().string("test").match(this.request); + } + + @Test(expected=AssertionError.class) + public void testStringNoMatch() throws Exception { + this.request.getBody().write("test".getBytes()); + + RequestMatchers.content().string("Test").match(this.request); + } + + @Test + public void testBytes() throws Exception { + byte[] content = "test".getBytes(); + this.request.getBody().write(content); + + RequestMatchers.content().bytes(content).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testBytesNoMatch() throws Exception { + this.request.getBody().write("test".getBytes()); + + RequestMatchers.content().bytes("Test".getBytes()).match(this.request); + } + + @Test + public void testXml() throws Exception { + String content = "bazbazz"; + this.request.getBody().write(content.getBytes()); + + RequestMatchers.content().xml(content).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testXmlNoMatch() throws Exception { + this.request.getBody().write("11".getBytes()); + + RequestMatchers.content().xml("22").match(this.request); + } + + @Test + public void testNodeMatcher() throws Exception { + String content = "baz"; + this.request.getBody().write(content.getBytes()); + + RequestMatchers.content().node(hasXPath("/foo/bar")).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testNodeMatcherNoMatch() throws Exception { + String content = "baz"; + this.request.getBody().write(content.getBytes()); + + RequestMatchers.content().node(hasXPath("/foo/bar/bar")).match(this.request); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/match/JsonPathRequestMatchersTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/match/JsonPathRequestMatchersTests.java new file mode 100644 index 0000000000..21e1dc2f14 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/match/JsonPathRequestMatchersTests.java @@ -0,0 +1,93 @@ +/* + * 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.mock.client.match; + +import java.io.IOException; + +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.http.client.MockClientHttpRequest; +import org.springframework.test.web.mock.client.match.JsonPathRequestMatchers; + +/** + * Tests for {@link JsonPathRequestMatchers}. + * + * @author Rossen Stoyanchev + */ +public class JsonPathRequestMatchersTests { + + private static final String RESPONSE_CONTENT = "{\"foo\":\"bar\", \"qux\":[\"baz1\",\"baz2\"]}"; + + private MockClientHttpRequest request; + + @Before + public void setUp() throws IOException { + this.request = new MockClientHttpRequest(); + this.request.getBody().write(RESPONSE_CONTENT.getBytes()); + } + + @Test + public void value() throws Exception { + new JsonPathRequestMatchers("$.foo").value("bar").match(this.request); + } + + @Test(expected=AssertionError.class) + public void valueNoMatch() throws Exception { + new JsonPathRequestMatchers("$.foo").value("bogus").match(this.request); + } + + @Test + public void valueMatcher() throws Exception { + new JsonPathRequestMatchers("$.foo").value(Matchers.equalTo("bar")).match(this.request); + } + + @Test(expected=AssertionError.class) + public void valueMatcherNoMatch() throws Exception { + new JsonPathRequestMatchers("$.foo").value(Matchers.equalTo("bogus")).match(this.request); + } + + @Test + public void exists() throws Exception { + new JsonPathRequestMatchers("$.foo").exists().match(this.request); + } + + @Test(expected=AssertionError.class) + public void existsNoMatch() throws Exception { + new JsonPathRequestMatchers("$.bogus").exists().match(this.request); + } + + @Test + public void doesNotExist() throws Exception { + new JsonPathRequestMatchers("$.bogus").doesNotExist().match(this.request); + } + + @Test(expected=AssertionError.class) + public void doesNotExistNoMatch() throws Exception { + new JsonPathRequestMatchers("$.foo").doesNotExist().match(this.request); + } + + @Test + public void isArrayMatch() throws Exception { + new JsonPathRequestMatchers("$.qux").isArray().match(this.request); + } + + @Test(expected=AssertionError.class) + public void isArrayNoMatch() throws Exception { + new JsonPathRequestMatchers("$.bar").isArray().match(this.request); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/match/RequestMatchersTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/match/RequestMatchersTests.java new file mode 100644 index 0000000000..3bd0549126 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/match/RequestMatchersTests.java @@ -0,0 +1,139 @@ +/* + * 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.mock.client.match; + +import static org.hamcrest.Matchers.containsString; + +import java.net.URI; +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpMethod; +import org.springframework.mock.http.client.MockClientHttpRequest; +import org.springframework.test.web.mock.client.match.RequestMatchers; + +/** + * Tests for {@link RequestMatchers}. + * + * @author Craig Walls + * @author Rossen Stoyanchev + */ +public class RequestMatchersTests { + + private MockClientHttpRequest request; + + @Before + public void setUp() { + this.request = new MockClientHttpRequest(); + } + + @Test + public void requestTo() throws Exception { + this.request.setURI(new URI("http://foo.com/bar")); + + RequestMatchers.requestTo("http://foo.com/bar").match(this.request); + } + + @Test(expected=AssertionError.class) + public void requestToNoMatch() throws Exception { + this.request.setURI(new URI("http://foo.com/bar")); + + RequestMatchers.requestTo("http://foo.com/wrong").match(this.request); + } + + @Test + public void requestToContains() throws Exception { + this.request.setURI(new URI("http://foo.com/bar")); + + RequestMatchers.requestTo(containsString("bar")).match(this.request); + } + + @Test + public void method() throws Exception { + this.request.setMethod(HttpMethod.GET); + + RequestMatchers.method(HttpMethod.GET).match(this.request); + } + + @Test(expected=AssertionError.class) + public void methodNoMatch() throws Exception { + this.request.setMethod(HttpMethod.POST); + + RequestMatchers.method(HttpMethod.GET).match(this.request); + } + + @Test + public void header() throws Exception { + this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); + + RequestMatchers.header("foo", "bar", "baz").match(this.request); + } + + @Test(expected=AssertionError.class) + public void headerMissing() throws Exception { + RequestMatchers.header("foo", "bar").match(this.request); + } + + @Test(expected=AssertionError.class) + public void headerMissingValue() throws Exception { + this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); + + RequestMatchers.header("foo", "bad").match(this.request); + } + + @SuppressWarnings("unchecked") + @Test + public void headerContains() throws Exception { + this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); + + RequestMatchers.header("foo", containsString("ba")).match(this.request); + } + + @SuppressWarnings("unchecked") + @Test(expected=AssertionError.class) + public void headerContainsWithMissingHeader() throws Exception { + RequestMatchers.header("foo", containsString("baz")).match(this.request); + } + + @SuppressWarnings("unchecked") + @Test(expected=AssertionError.class) + public void headerContainsWithMissingValue() throws Exception { + this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); + + RequestMatchers.header("foo", containsString("bx")).match(this.request); + } + + @Test + public void headers() throws Exception { + this.request.getHeaders().put("foo", Arrays.asList("bar", "baz")); + + RequestMatchers.header("foo", "bar", "baz").match(this.request); + } + + @Test(expected=AssertionError.class) + public void headersWithMissingHeader() throws Exception { + RequestMatchers.header("foo", "bar").match(this.request); + } + + @Test(expected=AssertionError.class) + public void headersWithMissingValue() throws Exception { + this.request.getHeaders().put("foo", Arrays.asList("bar")); + + RequestMatchers.header("foo", "bar", "baz").match(this.request); + } + +} \ No newline at end of file diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/match/XpathRequestMatchersTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/match/XpathRequestMatchersTests.java new file mode 100644 index 0000000000..395d4f031f --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/match/XpathRequestMatchersTests.java @@ -0,0 +1,113 @@ +/* + * 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.mock.client.match; + +import java.io.IOException; + +import org.hamcrest.Matchers; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.http.client.MockClientHttpRequest; +import org.springframework.test.web.mock.client.match.XpathRequestMatchers; + +/** + * Tests for {@link XpathRequestMatchers}. + * + * @author Rossen Stoyanchev + */ +public class XpathRequestMatchersTests { + + private static final String RESPONSE_CONTENT = "111true"; + + private MockClientHttpRequest request; + + @Before + public void setUp() throws IOException { + this.request = new MockClientHttpRequest(); + this.request.getBody().write(RESPONSE_CONTENT.getBytes()); + } + + @Test + public void testNodeMatcher() throws Exception { + new XpathRequestMatchers("/foo/bar", null).node(Matchers.notNullValue()).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testNodeMatcherNoMatch() throws Exception { + new XpathRequestMatchers("/foo/bar", null).node(Matchers.nullValue()).match(this.request); + } + + @Test + public void testExists() throws Exception { + new XpathRequestMatchers("/foo/bar", null).exists().match(this.request); + } + + @Test(expected=AssertionError.class) + public void testExistsNoMatch() throws Exception { + new XpathRequestMatchers("/foo/Bar", null).exists().match(this.request); + } + + @Test + public void testDoesNotExist() throws Exception { + new XpathRequestMatchers("/foo/Bar", null).doesNotExist().match(this.request); + } + + @Test(expected=AssertionError.class) + public void testDoesNotExistNoMatch() throws Exception { + new XpathRequestMatchers("/foo/bar", null).doesNotExist().match(this.request); + } + + @Test + public void testNodeCount() throws Exception { + new XpathRequestMatchers("/foo/bar", null).nodeCount(2).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testNodeCountNoMatch() throws Exception { + new XpathRequestMatchers("/foo/bar", null).nodeCount(1).match(this.request); + } + + @Test + public void testString() throws Exception { + new XpathRequestMatchers("/foo/bar[1]", null).string("111").match(this.request); + } + + @Test(expected=AssertionError.class) + public void testStringNoMatch() throws Exception { + new XpathRequestMatchers("/foo/bar[1]", null).string("112").match(this.request); + } + + @Test + public void testNumber() throws Exception { + new XpathRequestMatchers("/foo/bar[1]", null).number(111.0).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testNumberNoMatch() throws Exception { + new XpathRequestMatchers("/foo/bar[1]", null).number(111.1).match(this.request); + } + + @Test + public void testBoolean() throws Exception { + new XpathRequestMatchers("/foo/bar[2]", null).booleanValue(true).match(this.request); + } + + @Test(expected=AssertionError.class) + public void testBooleanNoMatch() throws Exception { + new XpathRequestMatchers("/foo/bar[2]", null).booleanValue(false).match(this.request); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/response/ResponseCreatorsTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/response/ResponseCreatorsTests.java new file mode 100644 index 0000000000..38ba862070 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/response/ResponseCreatorsTests.java @@ -0,0 +1,130 @@ +/* + * 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.mock.client.response; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.net.URI; + +import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.mock.http.client.MockClientHttpResponse; +import org.springframework.test.web.mock.client.response.DefaultResponseCreator; +import org.springframework.test.web.mock.client.response.ResponseCreators; +import org.springframework.util.FileCopyUtils; + +/** + * Tests for the {@link ResponseCreators} static factory methods. + * + * @author Rossen Stoyanchev + */ +public class ResponseCreatorsTests { + + @Test + public void success() throws Exception { + MockClientHttpResponse response = (MockClientHttpResponse) ResponseCreators.withSuccess().createResponse(null); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertTrue(response.getHeaders().isEmpty()); + assertNull(response.getBody()); + } + + @Test + public void successWithContent() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withSuccess("foo", MediaType.TEXT_PLAIN); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertEquals(MediaType.TEXT_PLAIN, response.getHeaders().getContentType()); + assertArrayEquals("foo".getBytes(), FileCopyUtils.copyToByteArray(response.getBody())); + } + + @Test + public void successWithContentWithoutContentType() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withSuccess("foo", null); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNull(response.getHeaders().getContentType()); + assertArrayEquals("foo".getBytes(), FileCopyUtils.copyToByteArray(response.getBody())); + } + + @Test + public void created() throws Exception { + URI location = new URI("/foo"); + DefaultResponseCreator responseCreator = ResponseCreators.withCreatedEntity(location); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.CREATED, response.getStatusCode()); + assertEquals(location, response.getHeaders().getLocation()); + assertNull(response.getBody()); + } + + @Test + public void noContent() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withNoContent(); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.NO_CONTENT, response.getStatusCode()); + assertTrue(response.getHeaders().isEmpty()); + assertNull(response.getBody()); + } + + @Test + public void badRequest() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withBadRequest(); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.BAD_REQUEST, response.getStatusCode()); + assertTrue(response.getHeaders().isEmpty()); + assertNull(response.getBody()); + } + + @Test + public void unauthorized() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withUnauthorizedRequest(); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); + assertTrue(response.getHeaders().isEmpty()); + assertNull(response.getBody()); + } + + @Test + public void serverError() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withServerError(); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode()); + assertTrue(response.getHeaders().isEmpty()); + assertNull(response.getBody()); + } + + @Test + public void withStatus() throws Exception { + DefaultResponseCreator responseCreator = ResponseCreators.withStatus(HttpStatus.FORBIDDEN); + MockClientHttpResponse response = (MockClientHttpResponse) responseCreator.createResponse(null); + + assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode()); + assertTrue(response.getHeaders().isEmpty()); + assertNull(response.getBody()); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/SampleTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/SampleTests.java new file mode 100644 index 0000000000..1b838cdc68 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/SampleTests.java @@ -0,0 +1,116 @@ +/* + * 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.mock.client.samples; + +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.mock.client.match.RequestMatchers.method; +import static org.springframework.test.web.mock.client.match.RequestMatchers.requestTo; +import static org.springframework.test.web.mock.client.response.ResponseCreators.withSuccess; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +/** + * Examples to demonstrate writing client-side REST tests with Spring MVC Test. + * While the tests in this class invoke the RestTemplate directly, in actual + * tests the RestTemplate may likely be invoked indirectly, i.e. through client + * code. + * + * @author Rossen Stoyanchev + */ +public class SampleTests { + + private MockRestServiceServer mockServer; + + private RestTemplate restTemplate; + + @Before + public void setup() { + this.restTemplate = new RestTemplate(); + this.mockServer = MockRestServiceServer.createServer(this.restTemplate); + } + + @Test + public void performGet() throws Exception { + + String responseBody = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}"; + + this.mockServer.expect(requestTo("/composers/42")).andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); + + @SuppressWarnings("unused") + Person ludwig = restTemplate.getForObject("/composers/{id}", Person.class, 42); + + // person.getName().equals("Ludwig van Beethoven") + // person.getDouble().equals(1.6035) + + this.mockServer.verify(); + } + + @Test + public void performGetWithResponseBodyFromFile() throws Exception { + + Resource responseBody = new ClassPathResource("ludwig.json", this.getClass()); + + this.mockServer.expect(requestTo("/composers/42")).andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess(responseBody, MediaType.APPLICATION_JSON)); + + @SuppressWarnings("unused") + Person ludwig = restTemplate.getForObject("/composers/{id}", Person.class, 42); + + // hotel.getId() == 42 + // hotel.getName().equals("Holiday Inn") + + this.mockServer.verify(); + } + + @Test + public void verify() { + + this.mockServer.expect(requestTo("/number")).andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess("1", MediaType.TEXT_PLAIN)); + + this.mockServer.expect(requestTo("/number")).andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess("2", MediaType.TEXT_PLAIN)); + + this.mockServer.expect(requestTo("/number")).andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess("4", MediaType.TEXT_PLAIN)); + + this.mockServer.expect(requestTo("/number")).andExpect(method(HttpMethod.GET)) + .andRespond(withSuccess("8", MediaType.TEXT_PLAIN)); + + @SuppressWarnings("unused") + String result = this.restTemplate.getForObject("/number", String.class); + // result == "1" + + result = this.restTemplate.getForObject("/number", String.class); + // result == "2" + + try { + this.mockServer.verify(); + } + catch (AssertionError error) { + assertTrue(error.getMessage(), error.getMessage().contains("2 out of 4 were executed")); + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/ContentRequestMatcherTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/ContentRequestMatcherTests.java new file mode 100644 index 0000000000..6f118f9f23 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/ContentRequestMatcherTests.java @@ -0,0 +1,103 @@ +/* + * 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.mock.client.samples.matchers; + +import static org.hamcrest.Matchers.startsWith; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.mock.client.match.RequestMatchers.content; +import static org.springframework.test.web.mock.client.response.ResponseCreators.withSuccess; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +/** + * Examples of defining expectations on request content and content type. + * + * @author Rossen Stoyanchev + * + * @see JsonPathRequestMatcherTests + * @see XmlContentRequestMatcherTests + * @see XpathRequestMatcherTests + */ +public class ContentRequestMatcherTests { + + private MockRestServiceServer mockServer; + + private RestTemplate restTemplate; + + @Before + public void setup() { + List> converters = new ArrayList>(); + converters.add(new StringHttpMessageConverter()); + converters.add(new MappingJacksonHttpMessageConverter()); + + this.restTemplate = new RestTemplate(); + this.restTemplate.setMessageConverters(converters); + + this.mockServer = MockRestServiceServer.createServer(this.restTemplate); + } + + @Test + public void contentType() throws Exception { + this.mockServer.expect(content().mimeType("application/json;charset=UTF-8")).andRespond(withSuccess()); + this.restTemplate.put(new URI("/foo"), new Person()); + this.mockServer.verify(); + } + + @Test + public void contentTypeNoMatch() throws Exception { + this.mockServer.expect(content().mimeType("application/json;charset=UTF-8")).andRespond(withSuccess()); + try { + this.restTemplate.put(new URI("/foo"), "foo"); + } + catch (AssertionError error) { + String message = error.getMessage(); + assertTrue(message, message.startsWith("Content type expected:")); + } + } + + @Test + public void contentAsString() throws Exception { + this.mockServer.expect(content().string("foo")).andRespond(withSuccess()); + this.restTemplate.put(new URI("/foo"), "foo"); + this.mockServer.verify(); + } + + @Test + public void contentStringStartsWith() throws Exception { + this.mockServer.expect(content().string(startsWith("foo"))).andRespond(withSuccess()); + this.restTemplate.put(new URI("/foo"), "foo123"); + this.mockServer.verify(); + } + + @Test + public void contentAsBytes() throws Exception { + this.mockServer.expect(content().bytes("foo".getBytes())).andRespond(withSuccess()); + this.restTemplate.put(new URI("/foo"), "foo"); + this.mockServer.verify(); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/HeaderRequestMatcherTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/HeaderRequestMatcherTests.java new file mode 100644 index 0000000000..b69fec46ff --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/HeaderRequestMatcherTests.java @@ -0,0 +1,85 @@ +/* + * 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.mock.client.samples.matchers; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.mock.client.match.RequestMatchers.header; +import static org.springframework.test.web.mock.client.match.RequestMatchers.requestTo; +import static org.springframework.test.web.mock.client.response.ResponseCreators.withSuccess; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.StringHttpMessageConverter; +import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +/** + * Examples of defining expectations on request headers. + * + * @author Rossen Stoyanchev + */ +public class HeaderRequestMatcherTests { + + private static final String RESPONSE_BODY = "{\"name\" : \"Ludwig van Beethoven\", \"someDouble\" : \"1.6035\"}"; + + private MockRestServiceServer mockServer; + + private RestTemplate restTemplate; + + @Before + public void setup() { + List> converters = new ArrayList>(); + converters.add(new StringHttpMessageConverter()); + converters.add(new MappingJacksonHttpMessageConverter()); + + this.restTemplate = new RestTemplate(); + this.restTemplate.setMessageConverters(converters); + + this.mockServer = MockRestServiceServer.createServer(this.restTemplate); + } + + @Test + public void testString() throws Exception { + + this.mockServer.expect(requestTo("/person/1")) + .andExpect(header("Accept", "application/json")) + .andRespond(withSuccess(RESPONSE_BODY, MediaType.APPLICATION_JSON)); + + this.restTemplate.getForObject(new URI("/person/1"), Person.class); + this.mockServer.verify(); + } + + @SuppressWarnings("unchecked") + @Test + public void testStringContains() throws Exception { + + this.mockServer.expect(requestTo("/person/1")) + .andExpect(header("Accept", containsString("json"))) + .andRespond(withSuccess(RESPONSE_BODY, MediaType.APPLICATION_JSON)); + + this.restTemplate.getForObject(new URI("/person/1"), Person.class); + this.mockServer.verify(); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/JsonPathRequestMatcherTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/JsonPathRequestMatcherTests.java new file mode 100644 index 0000000000..69242ebbb1 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/JsonPathRequestMatcherTests.java @@ -0,0 +1,150 @@ +/* + * 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.mock.client.samples.matchers; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.isIn; +import static org.hamcrest.Matchers.startsWith; +import static org.springframework.test.web.mock.client.match.RequestMatchers.content; +import static org.springframework.test.web.mock.client.match.RequestMatchers.jsonPath; +import static org.springframework.test.web.mock.client.match.RequestMatchers.requestTo; +import static org.springframework.test.web.mock.client.response.ResponseCreators.withSuccess; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJacksonHttpMessageConverter; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.client.MockRestServiceServer; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +/** + * Examples of defining expectations on JSON request content with + * JSONPath expressions. + * + * @author Rossen Stoyanchev + */ +public class JsonPathRequestMatcherTests { + + private MockRestServiceServer mockServer; + + private RestTemplate restTemplate; + + private MultiValueMap people; + + + @Before + public void setup() { + this.people = new LinkedMultiValueMap(); + this.people.add("composers", new Person("Johann Sebastian Bach")); + this.people.add("composers", new Person("Johannes Brahms")); + this.people.add("composers", new Person("Edvard Grieg")); + this.people.add("composers", new Person("Robert Schumann")); + this.people.add("performers", new Person("Vladimir Ashkenazy")); + this.people.add("performers", new Person("Yehudi Menuhin")); + + List> converters = new ArrayList>(); + converters.add(new MappingJacksonHttpMessageConverter()); + + this.restTemplate = new RestTemplate(); + this.restTemplate.setMessageConverters(converters); + + this.mockServer = MockRestServiceServer.createServer(this.restTemplate); + } + + @Test + public void testExists() throws Exception { + this.mockServer.expect(requestTo("/composers")) + .andExpect(content().mimeType("application/json;charset=UTF-8")) + .andExpect(jsonPath("$.composers[0]").exists()) + .andExpect(jsonPath("$.composers[1]").exists()) + .andExpect(jsonPath("$.composers[2]").exists()) + .andExpect(jsonPath("$.composers[3]").exists()) + .andRespond(withSuccess()); + + this.restTemplate.put(new URI("/composers"), this.people); + this.mockServer.verify(); + } + + @Test + public void testDoesNotExist() throws Exception { + this.mockServer.expect(requestTo("/composers")) + .andExpect(content().mimeType("application/json;charset=UTF-8")) + .andExpect(jsonPath("$.composers[?(@.name = 'Edvard Grieeeeeeg')]").doesNotExist()) + .andExpect(jsonPath("$.composers[?(@.name = 'Robert Schuuuuuuman')]").doesNotExist()) + .andExpect(jsonPath("$.composers[-1]").doesNotExist()) + .andExpect(jsonPath("$.composers[4]").doesNotExist()) + .andRespond(withSuccess()); + + this.restTemplate.put(new URI("/composers"), this.people); + this.mockServer.verify(); + } + + @Test + public void testEqualTo() throws Exception { + this.mockServer.expect(requestTo("/composers")) + .andExpect(content().mimeType("application/json;charset=UTF-8")) + .andExpect(jsonPath("$.composers[0].name").value("Johann Sebastian Bach")) + .andExpect(jsonPath("$.performers[1].name").value("Yehudi Menuhin")) + .andExpect(jsonPath("$.composers[0].name").value(equalTo("Johann Sebastian Bach"))) // Hamcrest + .andExpect(jsonPath("$.performers[1].name").value(equalTo("Yehudi Menuhin"))) // Hamcrest + .andRespond(withSuccess()); + + this.restTemplate.put(new URI("/composers"), this.people); + this.mockServer.verify(); + } + + @Test + public void testHamcrestMatcher() throws Exception { + this.mockServer.expect(requestTo("/composers")) + .andExpect(content().mimeType("application/json;charset=UTF-8")) + .andExpect(jsonPath("$.composers[0].name", startsWith("Johann"))) + .andExpect(jsonPath("$.performers[0].name", endsWith("Ashkenazy"))) + .andExpect(jsonPath("$.performers[1].name", containsString("di Me"))) + .andExpect(jsonPath("$.composers[1].name", isIn(Arrays.asList("Johann Sebastian Bach", "Johannes Brahms")))) + .andRespond(withSuccess()); + + this.restTemplate.put(new URI("/composers"), this.people); + this.mockServer.verify(); + } + + @Test + public void testHamcrestMatcherWithParameterizedJsonPath() throws Exception { + String composerName = "$.composers[%s].name"; + String performerName = "$.performers[%s].name"; + + this.mockServer.expect(requestTo("/composers")) + .andExpect(content().mimeType("application/json;charset=UTF-8")) + .andExpect(jsonPath(composerName, 0).value(startsWith("Johann"))) + .andExpect(jsonPath(performerName, 0).value(endsWith("Ashkenazy"))) + .andExpect(jsonPath(performerName, 1).value(containsString("di Me"))) + .andExpect(jsonPath(composerName, 1).value(isIn(Arrays.asList("Johann Sebastian Bach", "Johannes Brahms")))) + .andRespond(withSuccess()); + + this.restTemplate.put(new URI("/composers"), this.people); + this.mockServer.verify(); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/XmlContentRequestMatcherTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/XmlContentRequestMatcherTests.java new file mode 100644 index 0000000000..6b76e7f3cd --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/XmlContentRequestMatcherTests.java @@ -0,0 +1,133 @@ +/* + * 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.mock.client.samples.matchers; + +import static org.hamcrest.Matchers.hasXPath; +import static org.springframework.test.web.mock.client.match.RequestMatchers.content; +import static org.springframework.test.web.mock.client.match.RequestMatchers.requestTo; +import static org.springframework.test.web.mock.client.response.ResponseCreators.withSuccess; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + + +/** + * Examples of defining expectations on XML request content with XMLUnit. + * + * @author Rossen Stoyanchev + * + * @see ContentRequestMatcherTests + * @see XpathRequestMatcherTests + */ +public class XmlContentRequestMatcherTests { + + private static final String PEOPLE_XML = + "" + + "" + + "Johann Sebastian Bachfalse21.0" + + "Johannes Brahmsfalse0.0025" + + "Edvard Griegfalse1.6035" + + "Robert SchumannfalseNaN" + + ""; + + private MockRestServiceServer mockServer; + + private RestTemplate restTemplate; + + private PeopleWrapper people; + + + @Before + public void setup() { + + List composers = Arrays.asList( + new Person("Johann Sebastian Bach").setSomeDouble(21), + new Person("Johannes Brahms").setSomeDouble(.0025), + new Person("Edvard Grieg").setSomeDouble(1.6035), + new Person("Robert Schumann").setSomeDouble(Double.NaN)); + + this.people = new PeopleWrapper(composers); + + List> converters = new ArrayList>(); + converters.add(new Jaxb2RootElementHttpMessageConverter()); + + this.restTemplate = new RestTemplate(); + this.restTemplate.setMessageConverters(converters); + + this.mockServer = MockRestServiceServer.createServer(this.restTemplate); + } + + @Test + public void testXmlEqualTo() throws Exception { + this.mockServer.expect(requestTo("/composers")) + .andExpect(content().mimeType("application/xml")) + .andExpect(content().xml(PEOPLE_XML)) + .andRespond(withSuccess()); + + this.restTemplate.put(new URI("/composers"), this.people); + this.mockServer.verify(); + } + + @Test + public void testHamcrestNodeMatcher() throws Exception { + + this.mockServer.expect(requestTo("/composers")) + .andExpect(content().mimeType("application/xml")) + .andExpect(content().node(hasXPath("/people/composers/composer[1]"))) + .andRespond(withSuccess()); + + this.restTemplate.put(new URI("/composers"), this.people); + this.mockServer.verify(); + } + + + @SuppressWarnings("unused") + @XmlRootElement(name="people") + @XmlAccessorType(XmlAccessType.FIELD) + private static class PeopleWrapper { + + @XmlElementWrapper(name="composers") + @XmlElement(name="composer") + private List composers; + + public PeopleWrapper() { + } + + public PeopleWrapper(List composers) { + this.composers = composers; + } + + public List getComposers() { + return this.composers; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/XpathRequestMatcherTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/XpathRequestMatcherTests.java new file mode 100644 index 0000000000..8f4fed3d10 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/client/samples/matchers/XpathRequestMatcherTests.java @@ -0,0 +1,233 @@ +/* + * 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.mock.client.samples.matchers; + +import static org.hamcrest.Matchers.closeTo; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.lessThan; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.springframework.test.web.mock.client.match.RequestMatchers.content; +import static org.springframework.test.web.mock.client.match.RequestMatchers.requestTo; +import static org.springframework.test.web.mock.client.match.RequestMatchers.xpath; +import static org.springframework.test.web.mock.client.response.ResponseCreators.withSuccess; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.client.MockRestServiceServer; +import org.springframework.web.client.RestTemplate; + +/** + * Examples of defining expectations on XML request content with XPath expressions. + * + * @author Rossen Stoyanchev + * + * @see ContentRequestMatcherTests + * @see XmlContentRequestMatcherTests + */ +public class XpathRequestMatcherTests { + + private static final Map NS = + Collections.singletonMap("ns", "http://example.org/music/people"); + + private MockRestServiceServer mockServer; + + private RestTemplate restTemplate; + + private PeopleWrapper people; + + @Before + public void setup() { + + List composers = Arrays.asList( + new Person("Johann Sebastian Bach").setSomeDouble(21), + new Person("Johannes Brahms").setSomeDouble(.0025), + new Person("Edvard Grieg").setSomeDouble(1.6035), + new Person("Robert Schumann").setSomeDouble(Double.NaN)); + + List performers = Arrays.asList( + new Person("Vladimir Ashkenazy").setSomeBoolean(false), + new Person("Yehudi Menuhin").setSomeBoolean(true)); + + this.people = new PeopleWrapper(composers, performers); + + List> converters = new ArrayList>(); + converters.add(new Jaxb2RootElementHttpMessageConverter()); + + this.restTemplate = new RestTemplate(); + this.restTemplate.setMessageConverters(converters); + + this.mockServer = MockRestServiceServer.createServer(this.restTemplate); + } + + @Test + public void testExists() throws Exception { + + String composer = "/ns:people/composers/composer[%s]"; + String performer = "/ns:people/performers/performer[%s]"; + + this.mockServer.expect(requestTo("/composers")) + .andExpect(content().mimeType("application/xml")) + .andExpect(xpath(composer, NS, 1).exists()) + .andExpect(xpath(composer, NS, 2).exists()) + .andExpect(xpath(composer, NS, 3).exists()) + .andExpect(xpath(composer, NS, 4).exists()) + .andExpect(xpath(performer, NS, 1).exists()) + .andExpect(xpath(performer, NS, 2).exists()) + .andRespond(withSuccess()); + + this.restTemplate.put(new URI("/composers"), this.people); + this.mockServer.verify(); + } + + @Test + public void testDoesNotExist() throws Exception { + + String composer = "/ns:people/composers/composer[%s]"; + String performer = "/ns:people/performers/performer[%s]"; + + this.mockServer.expect(requestTo("/composers")) + .andExpect(content().mimeType("application/xml")) + .andExpect(xpath(composer, NS, 0).doesNotExist()) + .andExpect(xpath(composer, NS, 5).doesNotExist()) + .andExpect(xpath(performer, NS, 0).doesNotExist()) + .andExpect(xpath(performer, NS, 3).doesNotExist()) + .andRespond(withSuccess()); + + this.restTemplate.put(new URI("/composers"), this.people); + this.mockServer.verify(); + } + + @Test + public void testString() throws Exception { + + String composerName = "/ns:people/composers/composer[%s]/name"; + String performerName = "/ns:people/performers/performer[%s]/name"; + + this.mockServer.expect(requestTo("/composers")) + .andExpect(content().mimeType("application/xml")) + .andExpect(xpath(composerName, NS, 1).string("Johann Sebastian Bach")) + .andExpect(xpath(composerName, NS, 2).string("Johannes Brahms")) + .andExpect(xpath(composerName, NS, 3).string("Edvard Grieg")) + .andExpect(xpath(composerName, NS, 4).string("Robert Schumann")) + .andExpect(xpath(performerName, NS, 1).string("Vladimir Ashkenazy")) + .andExpect(xpath(performerName, NS, 2).string("Yehudi Menuhin")) + .andExpect(xpath(composerName, NS, 1).string(equalTo("Johann Sebastian Bach"))) // Hamcrest.. + .andExpect(xpath(composerName, NS, 1).string(startsWith("Johann"))) // Hamcrest.. + .andExpect(xpath(composerName, NS, 1).string(notNullValue())) // Hamcrest.. + .andRespond(withSuccess()); + + this.restTemplate.put(new URI("/composers"), this.people); + this.mockServer.verify(); + } + + @Test + public void testNumber() throws Exception { + + String composerDouble = "/ns:people/composers/composer[%s]/someDouble"; + + this.mockServer.expect(requestTo("/composers")) + .andExpect(content().mimeType("application/xml")) + .andExpect(xpath(composerDouble, NS, 1).number(21d)) + .andExpect(xpath(composerDouble, NS, 2).number(.0025)) + .andExpect(xpath(composerDouble, NS, 3).number(1.6035)) + .andExpect(xpath(composerDouble, NS, 4).number(Double.NaN)) + .andExpect(xpath(composerDouble, NS, 1).number(equalTo(21d))) // Hamcrest.. + .andExpect(xpath(composerDouble, NS, 3).number(closeTo(1.6, .01))) // Hamcrest.. + .andRespond(withSuccess()); + + this.restTemplate.put(new URI("/composers"), this.people); + this.mockServer.verify(); + } + + @Test + public void testBoolean() throws Exception { + + String performerBooleanValue = "/ns:people/performers/performer[%s]/someBoolean"; + + this.mockServer.expect(requestTo("/composers")) + .andExpect(content().mimeType("application/xml")) + .andExpect(xpath(performerBooleanValue, NS, 1).booleanValue(false)) + .andExpect(xpath(performerBooleanValue, NS, 2).booleanValue(true)) + .andRespond(withSuccess()); + + this.restTemplate.put(new URI("/composers"), this.people); + this.mockServer.verify(); + } + + @Test + public void testNodeCount() throws Exception { + + this.mockServer.expect(requestTo("/composers")) + .andExpect(content().mimeType("application/xml")) + .andExpect(xpath("/ns:people/composers/composer", NS).nodeCount(4)) + .andExpect(xpath("/ns:people/performers/performer", NS).nodeCount(2)) + .andExpect(xpath("/ns:people/composers/composer", NS).nodeCount(lessThan(5))) // Hamcrest.. + .andExpect(xpath("/ns:people/performers/performer", NS).nodeCount(greaterThan(0))) // Hamcrest.. + .andRespond(withSuccess()); + + this.restTemplate.put(new URI("/composers"), this.people); + this.mockServer.verify(); + } + + + @SuppressWarnings("unused") + @XmlRootElement(name="people", namespace="http://example.org/music/people") + @XmlAccessorType(XmlAccessType.FIELD) + private static class PeopleWrapper { + + @XmlElementWrapper(name="composers") + @XmlElement(name="composer") + private List composers; + + @XmlElementWrapper(name="performers") + @XmlElement(name="performer") + private List performers; + + public PeopleWrapper() { + } + + public PeopleWrapper(List composers, List performers) { + this.composers = composers; + this.performers = performers; + } + + public List getComposers() { + return this.composers; + } + + public List getPerformers() { + return this.performers; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/StubMvcResult.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/StubMvcResult.java new file mode 100644 index 0000000000..c7fc6f1da4 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/StubMvcResult.java @@ -0,0 +1,123 @@ +/* + * 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.mock.servlet; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.web.servlet.FlashMap; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + +/** + * A stub implementation of the {@link MvcResult} contract. + * + * @author Rossen Stoyanchev + */ +public class StubMvcResult implements MvcResult { + + private MockHttpServletRequest request; + + private Object handler; + + private HandlerInterceptor[] interceptors; + + private Exception resolvedException; + + private ModelAndView mav; + + private FlashMap flashMap; + + private MockHttpServletResponse response; + + public StubMvcResult(MockHttpServletRequest request, + Object handler, + HandlerInterceptor[] interceptors, + Exception resolvedException, + ModelAndView mav, + FlashMap flashMap, + MockHttpServletResponse response) { + this.request = request; + this.handler = handler; + this.interceptors = interceptors; + this.resolvedException = resolvedException; + this.mav = mav; + this.flashMap = flashMap; + this.response = response; + } + + public MockHttpServletRequest getRequest() { + return request; + } + + public Object getHandler() { + return handler; + } + + public HandlerInterceptor[] getInterceptors() { + return interceptors; + } + + public Exception getResolvedException() { + return resolvedException; + } + + public ModelAndView getModelAndView() { + return mav; + } + + public FlashMap getFlashMap() { + return flashMap; + } + + public MockHttpServletResponse getResponse() { + return response; + } + + public ModelAndView getMav() { + return mav; + } + + public void setMav(ModelAndView mav) { + this.mav = mav; + } + + public void setRequest(MockHttpServletRequest request) { + this.request = request; + } + + public void setHandler(Object handler) { + this.handler = handler; + } + + public void setInterceptors(HandlerInterceptor[] interceptors) { + this.interceptors = interceptors; + } + + public void setResolvedException(Exception resolvedException) { + this.resolvedException = resolvedException; + } + + public void setFlashMap(FlashMap flashMap) { + this.flashMap = flashMap; + } + + public void setResponse(MockHttpServletResponse response) { + this.response = response; + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/request/MockHttpServletRequestBuilderTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/request/MockHttpServletRequestBuilderTests.java new file mode 100644 index 0000000000..eef87f24ca --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/request/MockHttpServletRequestBuilderTests.java @@ -0,0 +1,390 @@ +/* + * 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.mock.servlet.request; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.net.URI; +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.http.Cookie; + +import org.junit.Before; +import org.junit.Test; +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.MockHttpSession; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.web.mock.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.util.FileCopyUtils; +import org.springframework.web.servlet.FlashMap; +import org.springframework.web.servlet.support.SessionFlashMapManager; + +/** + * Tests building a MockHttpServletRequest with {@link MockHttpServletRequestBuilder}. + * + * @author Rossen Stoyanchev + */ +public class MockHttpServletRequestBuilderTests { + + private MockHttpServletRequestBuilder builder; + + private ServletContext servletContext; + + + @Before + public void setUp() throws Exception { + this.builder = new MockHttpServletRequestBuilder(new URI("/foo/bar"), HttpMethod.GET); + servletContext = new MockServletContext(); + } + + @Test + public void method() { + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals("GET", request.getMethod()); + } + + @Test + public void uri() throws Exception { + URI uri = new URI("https://java.sun.com:8080/javase/6/docs/api/java/util/BitSet.html?foo=bar#and(java.util.BitSet)"); + this.builder = new MockHttpServletRequestBuilder(uri, HttpMethod.GET); + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals("https", request.getScheme()); + assertEquals("foo=bar", request.getQueryString()); + assertEquals("java.sun.com", request.getServerName()); + assertEquals(8080, request.getServerPort()); + assertEquals("/javase/6/docs/api/java/util/BitSet.html", request.getRequestURI()); + assertEquals("https://java.sun.com:8080/javase/6/docs/api/java/util/BitSet.html", + request.getRequestURL().toString()); + } + + @Test + public void requestUriEncodedPath() throws Exception { + this.builder = new MockHttpServletRequestBuilder(new URI("/foo%20bar"), HttpMethod.GET); + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals("/foo%20bar", request.getRequestURI()); + } + + @Test + public void contextPathEmpty() throws Exception { + this.builder = new MockHttpServletRequestBuilder(new URI("/foo"), HttpMethod.GET); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals("", request.getContextPath()); + assertEquals("", request.getServletPath()); + assertEquals("/foo", request.getPathInfo()); + } + + @Test + public void contextPathServletPathEmpty() throws Exception { + this.builder = new MockHttpServletRequestBuilder(new URI("/travel/hotels/42"), HttpMethod.GET); + this.builder.contextPath("/travel"); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals("/travel", request.getContextPath()); + assertEquals("", request.getServletPath()); + assertEquals("/hotels/42", request.getPathInfo()); + } + + @Test + public void contextPathServletPath() throws Exception { + this.builder = new MockHttpServletRequestBuilder(new URI("/travel/main/hotels/42"), HttpMethod.GET); + this.builder.contextPath("/travel"); + this.builder.servletPath("/main"); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals("/travel", request.getContextPath()); + assertEquals("/main", request.getServletPath()); + assertEquals("/hotels/42", request.getPathInfo()); + } + + @Test + public void contextPathServletPathInfoEmpty() throws Exception { + this.builder = new MockHttpServletRequestBuilder(new URI("/travel/hotels/42"), HttpMethod.GET); + + this.builder.contextPath("/travel"); + this.builder.servletPath("/hotels/42"); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals("/travel", request.getContextPath()); + assertEquals("/hotels/42", request.getServletPath()); + assertNull(request.getPathInfo()); + } + + @Test + public void contextPathServletPathInfo() throws Exception { + this.builder = new MockHttpServletRequestBuilder(new URI("/"), HttpMethod.GET); + this.builder.servletPath("/index.html"); + this.builder.pathInfo(null); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals("", request.getContextPath()); + assertEquals("/index.html", request.getServletPath()); + assertNull(request.getPathInfo()); + } + + @Test + public void contextPathServletPathInvalid() throws Exception { + + testContextPathServletPathInvalid("/Foo", "", "requestURI [/foo/bar] does not start with contextPath [/Foo]"); + testContextPathServletPathInvalid("foo", "", "Context path must start with a '/'"); + testContextPathServletPathInvalid("/foo/", "", "Context path must not end with a '/'"); + + testContextPathServletPathInvalid("/foo", "/Bar", "Invalid servletPath [/Bar] for requestURI [/foo/bar]"); + testContextPathServletPathInvalid("/foo", "bar", "Servlet path must start with a '/'"); + testContextPathServletPathInvalid("/foo", "/bar/", "Servlet path must not end with a '/'"); + } + + private void testContextPathServletPathInvalid(String contextPath, String servletPath, String message) { + try { + this.builder.contextPath(contextPath); + this.builder.servletPath(servletPath); + this.builder.buildRequest(this.servletContext); + } + catch (IllegalArgumentException ex) { + assertEquals(message, ex.getMessage()); + } + } + + @Test + public void requestUriAndFragment() throws Exception { + this.builder = new MockHttpServletRequestBuilder(new URI("/foo#bar"), HttpMethod.GET); + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals("/foo", request.getRequestURI()); + } + + @Test + public void requestParameter() { + this.builder.param("foo", "bar", "baz"); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + Map parameterMap = request.getParameterMap(); + + assertArrayEquals(new String[]{"bar", "baz"}, parameterMap.get("foo")); + } + + @Test + public void requestParameterFromQuery() throws Exception { + this.builder = new MockHttpServletRequestBuilder(new URI("/?foo=bar&foo=baz"), HttpMethod.GET); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + Map parameterMap = request.getParameterMap(); + + assertArrayEquals(new String[]{"bar", "baz"}, parameterMap.get("foo")); + assertEquals("foo=bar&foo=baz", request.getQueryString()); + } + + @Test + public void requestParametersFromQuery_i18n() throws Exception { + URI uri = new URI("/?foo=I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%C3%A0liz%C3%A6ti%C3%B8n"); + this.builder = new MockHttpServletRequestBuilder(uri, HttpMethod.GET); + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals("I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%C3%A0liz%C3%A6ti%C3%B8n", request.getParameter("foo")); + assertEquals("foo=I%C3%B1t%C3%ABrn%C3%A2ti%C3%B4n%C3%A0liz%C3%A6ti%C3%B8n", request.getQueryString()); + } + + @Test + public void acceptHeader() throws Exception { + this.builder.accept(MediaType.TEXT_HTML, MediaType.APPLICATION_XML); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + List accept = Collections.list(request.getHeaders("Accept")); + List result = MediaType.parseMediaTypes(accept.get(0)); + + assertEquals(1, accept.size()); + assertEquals("text/html", result.get(0).toString()); + assertEquals("application/xml", result.get(1).toString()); + } + + @Test + public void contentType() throws Exception { + this.builder.contentType(MediaType.TEXT_HTML); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + String contentType = request.getContentType(); + List contentTypes = Collections.list(request.getHeaders("Content-Type")); + + assertEquals("text/html", contentType); + assertEquals(1, contentTypes.size()); + assertEquals("text/html", contentTypes.get(0)); + } + + @Test + public void body() throws Exception { + byte[] body = "Hello World".getBytes("UTF-8"); + this.builder.body(body); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + byte[] result = FileCopyUtils.copyToByteArray(request.getInputStream()); + + assertArrayEquals(body, result); + } + + @Test + public void header() throws Exception { + this.builder.header("foo", "bar", "baz"); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + List headers = Collections.list(request.getHeaders("foo")); + + assertEquals(2, headers.size()); + assertEquals("bar", headers.get(0)); + assertEquals("baz", headers.get(1)); + } + + @Test + public void headers() throws Exception { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + httpHeaders.put("foo", Arrays.asList("bar", "baz")); + this.builder.headers(httpHeaders); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + List headers = Collections.list(request.getHeaders("foo")); + + assertEquals(2, headers.size()); + assertEquals("bar", headers.get(0)); + assertEquals("baz", headers.get(1)); + assertEquals(MediaType.APPLICATION_JSON.toString(), request.getHeader("Content-Type")); + } + + @Test + public void cookie() throws Exception { + Cookie cookie1 = new Cookie("foo", "bar"); + Cookie cookie2 = new Cookie("baz", "qux"); + this.builder.cookie(cookie1, cookie2); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + Cookie[] cookies = request.getCookies(); + + assertEquals(2, cookies.length); + assertEquals("foo", cookies[0].getName()); + assertEquals("bar", cookies[0].getValue()); + assertEquals("baz", cookies[1].getName()); + assertEquals("qux", cookies[1].getValue()); + } + + @Test + public void locale() throws Exception { + Locale locale = new Locale("nl", "nl"); + this.builder.locale(locale); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals(locale, request.getLocale()); + } + + @Test + public void characterEncoding() throws Exception { + String encoding = "UTF-8"; + this.builder.characterEncoding(encoding); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals(encoding, request.getCharacterEncoding()); + } + + @Test + public void requestAttribute() throws Exception { + this.builder.requestAttr("foo", "bar"); + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals("bar", request.getAttribute("foo")); + } + + @Test + public void sessionAttribute() throws Exception { + this.builder.sessionAttr("foo", "bar"); + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals("bar", request.getSession().getAttribute("foo")); + } + + @Test + public void sessionAttributes() throws Exception { + Map map = new HashMap(); + map.put("foo", "bar"); + this.builder.sessionAttrs(map); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals("bar", request.getSession().getAttribute("foo")); + } + + @Test + public void session() throws Exception { + MockHttpSession session = new MockHttpSession(this.servletContext); + session.setAttribute("foo", "bar"); + this.builder.session(session); + this.builder.sessionAttr("baz", "qux"); + + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals(session, request.getSession()); + assertEquals("bar", request.getSession().getAttribute("foo")); + assertEquals("qux", request.getSession().getAttribute("baz")); + } + + @Test + public void flashAttribute() throws Exception { + this.builder.flashAttr("foo", "bar"); + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + FlashMap flashMap = new SessionFlashMapManager().retrieveAndUpdate(request, null); + assertNotNull(flashMap); + assertEquals("bar", flashMap.get("foo")); + } + + @Test + public void principal() throws Exception { + User user = new User(); + this.builder.principal(user); + MockHttpServletRequest request = this.builder.buildRequest(this.servletContext); + + assertEquals(user, request.getUserPrincipal()); + } + + + private final class User implements Principal { + + public String getName() { + return "Foo"; + } + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/ContentResultMatchersTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/ContentResultMatchersTests.java new file mode 100644 index 0000000000..58598703cc --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/ContentResultMatchersTests.java @@ -0,0 +1,91 @@ +/* + * 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.mock.servlet.result; + +import org.hamcrest.Matchers; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.mock.servlet.StubMvcResult; +import org.springframework.test.web.mock.servlet.result.ContentResultMatchers; + +/** + * @author Rossen Stoyanchev + */ +public class ContentResultMatchersTests { + + @Test + public void typeMatches() throws Exception { + new ContentResultMatchers().mimeType("application/json;charset=UTF-8").match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void typeNoMatch() throws Exception { + new ContentResultMatchers().mimeType("text/plain").match(getStubMvcResult()); + } + + @Test + public void encoding() throws Exception { + new ContentResultMatchers().encoding("UTF-8").match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void encodingNoMatch() throws Exception { + new ContentResultMatchers().encoding("ISO-8859-1").match(getStubMvcResult()); + } + + @Test + public void string() throws Exception { + new ContentResultMatchers().string(new String(CONTENT.getBytes("UTF-8"))).match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void stringNoMatch() throws Exception { + new ContentResultMatchers().encoding("bogus").match(getStubMvcResult()); + } + + @Test + public void stringMatcher() throws Exception { + String content = new String(CONTENT.getBytes("UTF-8")); + new ContentResultMatchers().string(Matchers.equalTo(content)).match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void stringMatcherNoMatch() throws Exception { + new ContentResultMatchers().string(Matchers.equalTo("bogus")).match(getStubMvcResult()); + } + + @Test + public void bytes() throws Exception { + new ContentResultMatchers().bytes(CONTENT.getBytes("UTF-8")).match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void bytesNoMatch() throws Exception { + new ContentResultMatchers().bytes("bogus".getBytes()).match(getStubMvcResult()); + } + + + private static final String CONTENT = "{\"foo\":\"bar\"}"; + + private StubMvcResult getStubMvcResult() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + response.addHeader("Content-Type", "application/json; charset=UTF-8"); + response.getWriter().print(new String(CONTENT.getBytes("UTF-8"))); + return new StubMvcResult(null, null, null, null, null, null, response); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/FlashAttributeResultMatchersTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/FlashAttributeResultMatchersTests.java new file mode 100644 index 0000000000..b9ba287f91 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/FlashAttributeResultMatchersTests.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.test.web.mock.servlet.result; + +import org.junit.Test; +import org.springframework.test.web.mock.servlet.StubMvcResult; +import org.springframework.test.web.mock.servlet.result.FlashAttributeResultMatchers; +import org.springframework.web.servlet.FlashMap; + +/** + * @author Craig Walls + */ +public class FlashAttributeResultMatchersTests { + + @Test + public void attributeExists() throws Exception { + new FlashAttributeResultMatchers().attributeExists("good").match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void attributeExists_doesntExist() throws Exception { + new FlashAttributeResultMatchers().attributeExists("bad").match(getStubMvcResult()); + } + + @Test + public void attribute() throws Exception { + new FlashAttributeResultMatchers().attribute("good", "good").match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void attribute_incorrectValue() throws Exception { + new FlashAttributeResultMatchers().attribute("good", "not good").match(getStubMvcResult()); + } + + private StubMvcResult getStubMvcResult() { + FlashMap flashMap = new FlashMap(); + flashMap.put("good", "good"); + StubMvcResult mvcResult = new StubMvcResult(null, null, null, null, null, flashMap, null); + return mvcResult; + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/JsonPathResultMatchersTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/JsonPathResultMatchersTests.java new file mode 100644 index 0000000000..2e7ee9f09e --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/JsonPathResultMatchersTests.java @@ -0,0 +1,92 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.web.mock.servlet.result; + +import org.hamcrest.Matchers; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.mock.servlet.StubMvcResult; +import org.springframework.test.web.mock.servlet.result.JsonPathResultMatchers; + +/** + * Tests for {@link JsonPathResultMatchers}. + * + * @author Rossen Stoyanchev + */ +public class JsonPathResultMatchersTests { + + @Test + public void value() throws Exception { + new JsonPathResultMatchers("$.foo").value("bar").match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void valueNoMatch() throws Exception { + new JsonPathResultMatchers("$.foo").value("bogus").match(getStubMvcResult()); + } + + @Test + public void valueMatcher() throws Exception { + new JsonPathResultMatchers("$.foo").value(Matchers.equalTo("bar")).match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void valueMatcherNoMatch() throws Exception { + new JsonPathResultMatchers("$.foo").value(Matchers.equalTo("bogus")).match(getStubMvcResult()); + } + + @Test + public void exists() throws Exception { + new JsonPathResultMatchers("$.foo").exists().match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void existsNoMatch() throws Exception { + new JsonPathResultMatchers("$.bogus").exists().match(getStubMvcResult()); + } + + @Test + public void doesNotExist() throws Exception { + new JsonPathResultMatchers("$.bogus").doesNotExist().match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void doesNotExistNoMatch() throws Exception { + new JsonPathResultMatchers("$.foo").doesNotExist().match(getStubMvcResult()); + } + + @Test + public void isArrayMatch() throws Exception { + new JsonPathResultMatchers("$.qux").isArray().match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void isArrayNoMatch() throws Exception { + new JsonPathResultMatchers("$.bar").isArray().match(getStubMvcResult()); + } + + + private static final String RESPONSE_CONTENT = "{\"foo\":\"bar\", \"qux\":[\"baz1\",\"baz2\"]}"; + + private StubMvcResult getStubMvcResult() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + response.addHeader("Content-Type", "application/json"); + response.getWriter().print(new String(RESPONSE_CONTENT.getBytes("ISO-8859-1"))); + return new StubMvcResult(null, null, null, null, null, null, response); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/ModelResultMatchersTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/ModelResultMatchersTests.java new file mode 100644 index 0000000000..76fa203795 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/ModelResultMatchersTests.java @@ -0,0 +1,141 @@ +/* + * 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.mock.servlet.result; + +import static org.hamcrest.Matchers.is; + +import java.util.Date; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.StubMvcResult; +import org.springframework.test.web.mock.servlet.result.ModelResultMatchers; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.BindingResult; +import org.springframework.web.servlet.ModelAndView; + +/** + * @author Craig Walls + */ +public class ModelResultMatchersTests { + + private ModelResultMatchers matchers; + + private MvcResult mvcResult; + private MvcResult mvcResultWithError; + + @Before + public void setUp() throws Exception { + this.matchers = new ModelResultMatchers(); + + ModelAndView mav = new ModelAndView("view", "good", "good"); + BindingResult bindingResult = new BeanPropertyBindingResult("good", "good"); + mav.addObject(BindingResult.MODEL_KEY_PREFIX + "good", bindingResult); + + this.mvcResult = getMvcResult(mav); + + Date date = new Date(); + BindingResult bindingResultWithError = new BeanPropertyBindingResult(date, "date"); + bindingResultWithError.rejectValue("time", "error"); + + ModelAndView mavWithError = new ModelAndView("view", "good", "good"); + mavWithError.addObject("date", date); + mavWithError.addObject(BindingResult.MODEL_KEY_PREFIX + "date", bindingResultWithError); + + this.mvcResultWithError = getMvcResult(mavWithError); + } + + @Test + public void attributeExists() throws Exception { + this.matchers.attributeExists("good").match(this.mvcResult); + } + + @Test(expected=AssertionError.class) + public void attributeExists_doesNotExist() throws Exception { + this.matchers.attributeExists("bad").match(this.mvcResult); + } + + @Test + public void attribute_equal() throws Exception { + this.matchers.attribute("good", is("good")).match(this.mvcResult); + } + + @Test(expected=AssertionError.class) + public void attribute_notEqual() throws Exception { + this.matchers.attribute("good", is("bad")).match(this.mvcResult); + } + + @Test + public void hasNoErrors() throws Exception { + this.matchers.hasNoErrors().match(this.mvcResult); + } + + @Test(expected=AssertionError.class) + public void hasNoErrors_withErrors() throws Exception { + this.matchers.hasNoErrors().match(this.mvcResultWithError); + } + + @Test + public void attributeHasErrors() throws Exception { + this.matchers.attributeHasErrors("date").match(this.mvcResultWithError); + } + + @Test(expected=AssertionError.class) + public void attributeHasErrors_withoutErrors() throws Exception { + this.matchers.attributeHasErrors("good").match(this.mvcResultWithError); + } + + @Test + public void attributeHasNoErrors() throws Exception { + this.matchers.attributeHasNoErrors("good").match(this.mvcResult); + } + + @Test(expected=AssertionError.class) + public void attributeHasNoErrors_withoutAttribute() throws Exception { + this.matchers.attributeHasNoErrors("missing").match(this.mvcResultWithError); + } + + @Test(expected=AssertionError.class) + public void attributeHasNoErrors_withErrors() throws Exception { + this.matchers.attributeHasNoErrors("date").match(this.mvcResultWithError); + } + + @Test + public void attributeHasFieldErrors() throws Exception { + this.matchers.attributeHasFieldErrors("date", "time").match(this.mvcResultWithError); + } + + @Test(expected=AssertionError.class) + public void attributeHasFieldErrors_withoutAttribute() throws Exception { + this.matchers.attributeHasFieldErrors("missing", "bad").match(this.mvcResult); + } + + @Test(expected=AssertionError.class) + public void attributeHasFieldErrors_withoutErrorsForAttribute() throws Exception { + this.matchers.attributeHasFieldErrors("date", "time").match(this.mvcResult); + } + + @Test(expected=AssertionError.class) + public void attributeHasFieldErrors_withoutErrorsForField() throws Exception { + this.matchers.attributeHasFieldErrors("date", "good", "time").match(this.mvcResultWithError); + } + + private MvcResult getMvcResult(ModelAndView modelAndView) { + return new StubMvcResult(null, null, null, null, modelAndView, null, null); + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/PrintingResultHandlerTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/PrintingResultHandlerTests.java new file mode 100644 index 0000000000..6e84d3f5a7 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/PrintingResultHandlerTests.java @@ -0,0 +1,238 @@ +/* + * 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.mock.servlet.result; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.Cookie; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.mock.servlet.StubMvcResult; +import org.springframework.test.web.mock.servlet.result.PrintingResultHandler; +import org.springframework.util.Assert; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.DispatcherServlet; +import org.springframework.web.servlet.FlashMap; +import org.springframework.web.servlet.ModelAndView; + +/** + * Tests for {@link PrintingResultHandler}. + * + * @author Rossen Stoyanchev + */ +public class PrintingResultHandlerTests { + + private TestPrintingResultHandler handler; + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + private StubMvcResult mvcResult; + + + @Before + public void setup() { + this.handler = new TestPrintingResultHandler(); + this.request = new MockHttpServletRequest("GET", "/"); + this.response = new MockHttpServletResponse(); + this.mvcResult = new StubMvcResult(this.request, null, null, null, null, null, this.response); + } + + @Test + public void testPrintRequest() throws Exception { + this.request.addParameter("param", "paramValue"); + this.request.addHeader("header", "headerValue"); + + this.handler.handle(this.mvcResult); + + HttpHeaders headers = new HttpHeaders(); + headers.set("header", "headerValue"); + + assertValue("MockHttpServletRequest", "HTTP Method", this.request.getMethod()); + assertValue("MockHttpServletRequest", "Request URI", this.request.getRequestURI()); + assertValue("MockHttpServletRequest", "Parameters", this.request.getParameterMap()); + assertValue("MockHttpServletRequest", "Headers", headers); + } + + @Test + public void testPrintResponse() throws Exception { + this.response.setStatus(400, "error"); + this.response.addHeader("header", "headerValue"); + this.response.setContentType("text/plain"); + this.response.getWriter().print("content"); + this.response.setForwardedUrl("redirectFoo"); + this.response.sendRedirect("/redirectFoo"); + this.response.addCookie(new Cookie("cookie", "cookieValue")); + + this.handler.handle(this.mvcResult); + + HttpHeaders headers = new HttpHeaders(); + headers.set("header", "headerValue"); + headers.setContentType(MediaType.TEXT_PLAIN); + headers.setLocation(new URI("/redirectFoo")); + + assertValue("MockHttpServletResponse", "Status", this.response.getStatus()); + assertValue("MockHttpServletResponse", "Error message", response.getErrorMessage()); + assertValue("MockHttpServletResponse", "Headers", headers); + assertValue("MockHttpServletResponse", "Content type", this.response.getContentType()); + assertValue("MockHttpServletResponse", "Body", this.response.getContentAsString()); + assertValue("MockHttpServletResponse", "Forwarded URL", this.response.getForwardedUrl()); + assertValue("MockHttpServletResponse", "Redirected URL", this.response.getRedirectedUrl()); + } + + @Test + public void testPrintHandlerNull() throws Exception { + StubMvcResult mvcResult = new StubMvcResult(this.request, null, null, null, null, null, this.response); + this.handler.handle(mvcResult); + + assertValue("Handler", "Type", null); + } + + @Test + public void testPrintHandler() throws Exception { + this.mvcResult.setHandler(new Object()); + this.handler.handle(this.mvcResult); + + assertValue("Handler", "Type", Object.class.getName()); + } + + @Test + public void testPrintHandlerMethod() throws Exception { + HandlerMethod handlerMethod = new HandlerMethod(this, "handle"); + this.mvcResult.setHandler(handlerMethod); + this.handler.handle(mvcResult); + + assertValue("Handler", "Type", this.getClass().getName()); + assertValue("Handler", "Method", handlerMethod); + } + + @Test + public void testResolvedExceptionNull() throws Exception { + this.handler.handle(this.mvcResult); + + assertValue("Resolved Exception", "Type", null); + } + + @Test + public void testResolvedException() throws Exception { + this.mvcResult.setResolvedException(new Exception()); + this.handler.handle(this.mvcResult); + + assertValue("Resolved Exception", "Type", Exception.class.getName()); + } + + @Test + public void testModelAndViewNull() throws Exception { + this.handler.handle(this.mvcResult); + + assertValue("ModelAndView", "View name", null); + assertValue("ModelAndView", "View", null); + assertValue("ModelAndView", "Model", null); + } + + @Test + public void testModelAndView() throws Exception { + BindException bindException = new BindException(new Object(), "target"); + bindException.reject("errorCode"); + + ModelAndView mav = new ModelAndView("viewName"); + mav.addObject("attrName", "attrValue"); + mav.addObject(BindingResult.MODEL_KEY_PREFIX + "attrName", bindException); + + this.mvcResult.setMav(mav); + this.handler.handle(this.mvcResult); + + assertValue("ModelAndView", "View name", "viewName"); + assertValue("ModelAndView", "View", null); + assertValue("ModelAndView", "Attribute", "attrName"); + assertValue("ModelAndView", "value", "attrValue"); + assertValue("ModelAndView", "errors", bindException.getAllErrors()); + } + + @Test + public void testFlashMapNull() throws Exception { + this.handler.handle(mvcResult); + + assertValue("FlashMap", "Type", null); + } + + @Test + public void testFlashMap() throws Exception { + FlashMap flashMap = new FlashMap(); + flashMap.put("attrName", "attrValue"); + this.request.setAttribute(DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP", flashMap); + + this.handler.handle(this.mvcResult); + + assertValue("FlashMap", "Attribute", "attrName"); + assertValue("FlashMap", "value", "attrValue"); + } + + private void assertValue(String heading, String label, Object value) { + Map> printedValues = this.handler.getPrinter().printedValues; + assertTrue("Heading " + heading + " not printed", printedValues.containsKey(heading)); + assertEquals(value, printedValues.get(heading).get(label)); + } + + + private static class TestPrintingResultHandler extends PrintingResultHandler { + + public TestPrintingResultHandler() { + super(new TestResultValuePrinter()); + } + + @Override + public TestResultValuePrinter getPrinter() { + return (TestResultValuePrinter) super.getPrinter(); + } + + private static class TestResultValuePrinter implements ResultValuePrinter { + + private String printedHeading; + + private Map> printedValues = new HashMap>(); + + public void printHeading(String heading) { + this.printedHeading = heading; + this.printedValues.put(heading, new HashMap()); + } + + public void printValue(String label, Object value) { + Assert.notNull(this.printedHeading, + "Heading not printed before label " + label + " with value " + value); + this.printedValues.get(this.printedHeading).put(label, value); + } + } + } + + public void handle() { + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/StatusResultMatchersTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/StatusResultMatchersTests.java new file mode 100644 index 0000000000..186e23fd07 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/StatusResultMatchersTests.java @@ -0,0 +1,82 @@ +/* + * 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.mock.servlet.result; + +import static org.junit.Assert.fail; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.springframework.core.Conventions; +import org.springframework.http.HttpStatus; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.ResultMatcher; +import org.springframework.test.web.mock.servlet.StubMvcResult; +import org.springframework.test.web.mock.servlet.result.StatusResultMatchers; +import org.springframework.util.ReflectionUtils; +import org.springframework.util.StringUtils; + +/** + * Tests for {@link StatusResultMatchers}. + * + * @author Rossen Stoyanchev + */ +public class StatusResultMatchersTests { + + @Test + public void testHttpStatusCodeResultMatchers() throws Exception { + + StatusResultMatchers resultMatchers = new StatusResultMatchers(); + + List failures = new ArrayList(); + + for(HttpStatus status : HttpStatus.values()) { + MockHttpServletResponse response = new MockHttpServletResponse(); + response.setStatus(status.value()); + + String methodName = statusToMethodName(status); + Method method = StatusResultMatchers.class.getMethod(methodName); + try { + ResultMatcher matcher = (ResultMatcher) ReflectionUtils.invokeMethod(method, resultMatchers); + try { + MvcResult mvcResult = new StubMvcResult(new MockHttpServletRequest(), null, null, null, null, null, response); + matcher.match(mvcResult); + } + catch (AssertionError error) { + failures.add(error); + } + } + catch (Exception ex) { + throw new Exception("Failed to obtain ResultMatcher: " + method.toString(), ex); + } + } + + if (!failures.isEmpty()) { + fail("Failed status codes: " + failures); + } + } + + private String statusToMethodName(HttpStatus status) throws NoSuchMethodException { + String name = status.name().toLowerCase().replace("_", "-"); + return "is" + StringUtils.capitalize(Conventions.attributeNameToPropertyName(name)); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/XpathResultMatchersTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/XpathResultMatchersTests.java new file mode 100644 index 0000000000..244dea67cc --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/result/XpathResultMatchersTests.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.test.web.mock.servlet.result; + +import org.hamcrest.Matchers; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.mock.servlet.StubMvcResult; +import org.springframework.test.web.mock.servlet.result.XpathResultMatchers; + +/** + * Tests for {@link XpathResultMatchers}. + * + * @author Rossen Stoyanchev + */ +public class XpathResultMatchersTests { + + @Test + public void testNodeMatcher() throws Exception { + new XpathResultMatchers("/foo/bar", null).node(Matchers.notNullValue()).match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void testNodeMatcherNoMatch() throws Exception { + new XpathResultMatchers("/foo/bar", null).node(Matchers.nullValue()).match(getStubMvcResult()); + } + + @Test + public void testExists() throws Exception { + new XpathResultMatchers("/foo/bar", null).exists().match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void testExistsNoMatch() throws Exception { + new XpathResultMatchers("/foo/Bar", null).exists().match(getStubMvcResult()); + } + + @Test + public void testDoesNotExist() throws Exception { + new XpathResultMatchers("/foo/Bar", null).doesNotExist().match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void testDoesNotExistNoMatch() throws Exception { + new XpathResultMatchers("/foo/bar", null).doesNotExist().match(getStubMvcResult()); + } + + @Test + public void testNodeCount() throws Exception { + new XpathResultMatchers("/foo/bar", null).nodeCount(2).match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void testNodeCountNoMatch() throws Exception { + new XpathResultMatchers("/foo/bar", null).nodeCount(1).match(getStubMvcResult()); + } + + @Test + public void testString() throws Exception { + new XpathResultMatchers("/foo/bar[1]", null).string("111").match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void testStringNoMatch() throws Exception { + new XpathResultMatchers("/foo/bar[1]", null).string("112").match(getStubMvcResult()); + } + + @Test + public void testNumber() throws Exception { + new XpathResultMatchers("/foo/bar[1]", null).number(111.0).match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void testNumberNoMatch() throws Exception { + new XpathResultMatchers("/foo/bar[1]", null).number(111.1).match(getStubMvcResult()); + } + + @Test + public void testBoolean() throws Exception { + new XpathResultMatchers("/foo/bar[2]", null).booleanValue(true).match(getStubMvcResult()); + } + + @Test(expected=AssertionError.class) + public void testBooleanNoMatch() throws Exception { + new XpathResultMatchers("/foo/bar[2]", null).booleanValue(false).match(getStubMvcResult()); + } + + + private static final String RESPONSE_CONTENT = "111true"; + + private StubMvcResult getStubMvcResult() throws Exception { + MockHttpServletResponse response = new MockHttpServletResponse(); + response.addHeader("Content-Type", "application/json"); + response.getWriter().print(new String(RESPONSE_CONTENT.getBytes("ISO-8859-1"))); + return new StubMvcResult(null, null, null, null, null, null, response); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/GenericWebContextLoader.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/GenericWebContextLoader.java new file mode 100644 index 0000000000..55c477ccc3 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/GenericWebContextLoader.java @@ -0,0 +1,94 @@ +/* + * 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.mock.servlet.samples.context; + +import javax.servlet.RequestDispatcher; + +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.AnnotatedBeanDefinitionReader; +import org.springframework.context.annotation.AnnotationConfigUtils; +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.test.context.MergedContextConfiguration; +import org.springframework.test.context.support.AbstractContextLoader; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.GenericWebApplicationContext; + +/** + * This class is here temporarily until the TestContext framework provides + * support for WebApplicationContext yet: + * + * https://jira.springsource.org/browse/SPR-5243 + * + *

After that this class will no longer be needed. It's provided here as an example + * and to serve as a temporary solution. + */ +public class GenericWebContextLoader extends AbstractContextLoader { + protected final MockServletContext servletContext; + + public GenericWebContextLoader(String warRootDir, boolean isClasspathRelative) { + ResourceLoader resourceLoader = isClasspathRelative ? new DefaultResourceLoader() : new FileSystemResourceLoader(); + this.servletContext = initServletContext(warRootDir, resourceLoader); + } + + private MockServletContext initServletContext(String warRootDir, ResourceLoader resourceLoader) { + return new MockServletContext(warRootDir, resourceLoader) { + // Required for DefaultServletHttpRequestHandler... + public RequestDispatcher getNamedDispatcher(String path) { + return (path.equals("default")) ? new MockRequestDispatcher(path) : super.getNamedDispatcher(path); + } + }; + } + + public ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { + GenericWebApplicationContext context = new GenericWebApplicationContext(); + context.getEnvironment().setActiveProfiles(mergedConfig.getActiveProfiles()); + prepareContext(context); + loadBeanDefinitions(context, mergedConfig); + return context; + } + + public ApplicationContext loadContext(String... locations) throws Exception { + // should never be called + throw new UnsupportedOperationException(); + } + + protected void prepareContext(GenericWebApplicationContext context) { + this.servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, context); + context.setServletContext(this.servletContext); + } + + protected void loadBeanDefinitions(GenericWebApplicationContext context, String[] locations) { + new XmlBeanDefinitionReader(context).loadBeanDefinitions(locations); + AnnotationConfigUtils.registerAnnotationConfigProcessors(context); + context.refresh(); + context.registerShutdownHook(); + } + + protected void loadBeanDefinitions(GenericWebApplicationContext context, MergedContextConfiguration mergedConfig) { + new AnnotatedBeanDefinitionReader(context).register(mergedConfig.getClasses()); + loadBeanDefinitions(context, mergedConfig.getLocations()); + } + + @Override + protected String getResourceSuffix() { + return "-context.xml"; + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/JavaTestContextTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/JavaTestContextTests.java new file mode 100644 index 0000000000..722acba41e --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/JavaTestContextTests.java @@ -0,0 +1,63 @@ +/* + * 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.mock.servlet.samples.context; + +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.forwardedUrl; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.test.web.mock.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.ContextLoader; +import org.springframework.web.context.WebApplicationContext; + +/** + * Tests with Java configuration. + * + * The TestContext framework doesn't support WebApplicationContext yet: + * https://jira.springsource.org/browse/SPR-5243 + * + * A custom {@link ContextLoader} is used to load the WebApplicationContext. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(loader=WebContextLoader.class, classes={WebConfig.class}) +public class JavaTestContextTests { + + @Autowired + private WebApplicationContext wac; + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + } + + @Test + public void tilesDefinitions() throws Exception { + this.mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp")); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/SecurityRequestPostProcessors.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/SecurityRequestPostProcessors.java new file mode 100644 index 0000000000..8b19709567 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/SecurityRequestPostProcessors.java @@ -0,0 +1,231 @@ +/* + * 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.mock.servlet.samples.context; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.context.ApplicationContext; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.context.HttpRequestResponseHolder; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.security.web.context.SecurityContextRepository; +import org.springframework.test.web.mock.servlet.request.RequestPostProcessor; +import org.springframework.util.Assert; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.context.support.WebApplicationContextUtils; + +/** + * Demonstrates how to use a {@link RequestPostProcessor} to add + * request-building methods for establishing a security context for Spring + * Security. While these are just examples, + * official support + * for Spring Security is planned. + * + * @author Rob Winch + */ +final class SecurityRequestPostProcessors { + + /** + * Establish a security context for a user with the specified username. All + * details are declarative and do not require that the user actually exists. + * This means that the authorities or roles need to be specified too. + */ + public static UserRequestPostProcessor user(String username) { + return new UserRequestPostProcessor(username); + } + + /** + * Establish a security context for a user with the specified username. The + * additional details are obtained from the {@link UserDetailsService} + * declared in the {@link WebApplicationContext}. + */ + public static UserDetailsRequestPostProcessor userDeatilsService(String username) { + return new UserDetailsRequestPostProcessor(username); + } + + /** + * Establish a security context with the given {@link SecurityContext} and + * thus be authenticated with {@link SecurityContext#getAuthentication()}. + */ + public SecurityContextRequestPostProcessor securityContext(SecurityContext securityContext) { + return new SecurityContextRequestPostProcessor(securityContext); + } + + + /** Support class for {@link RequestPostProcessor}'s that establish a Spring Security context */ + private static abstract class SecurityContextRequestPostProcessorSupport { + + private SecurityContextRepository repository = new HttpSessionSecurityContextRepository(); + + final void save(Authentication authentication, HttpServletRequest request) { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(authentication); + save(securityContext, request); + } + + final void save(SecurityContext securityContext, HttpServletRequest request) { + HttpServletResponse response = new MockHttpServletResponse(); + + HttpRequestResponseHolder requestResponseHolder = new HttpRequestResponseHolder(request, response); + this.repository.loadContext(requestResponseHolder); + + request = requestResponseHolder.getRequest(); + response = requestResponseHolder.getResponse(); + + this.repository.saveContext(securityContext, request, response); + } + } + + public final static class SecurityContextRequestPostProcessor + extends SecurityContextRequestPostProcessorSupport implements RequestPostProcessor { + + private final SecurityContext securityContext; + + private SecurityContextRequestPostProcessor(SecurityContext securityContext) { + this.securityContext = securityContext; + } + + public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { + save(this.securityContext,request); + return request; + } + } + + public final static class UserRequestPostProcessor + extends SecurityContextRequestPostProcessorSupport implements RequestPostProcessor { + + private final String username; + + private String rolePrefix = "ROLE_"; + + private Object credentials; + + private List authorities = new ArrayList(); + + private UserRequestPostProcessor(String username) { + Assert.notNull(username, "username cannot be null"); + this.username = username; + } + + /** + * Sets the prefix to append to each role if the role does not already start with + * the prefix. If no prefix is desired, an empty String or null can be used. + */ + public UserRequestPostProcessor rolePrefix(String rolePrefix) { + this.rolePrefix = rolePrefix; + return this; + } + + /** + * Specify the roles of the user to authenticate as. This method is similar to + * {@link #authorities(GrantedAuthority...)}, but just not as flexible. + * + * @param roles The roles to populate. Note that if the role does not start with + * {@link #rolePrefix(String)} it will automatically be prepended. This means by + * default {@code roles("ROLE_USER")} and {@code roles("USER")} are equivalent. + * @see #authorities(GrantedAuthority...) + * @see #rolePrefix(String) + */ + public UserRequestPostProcessor roles(String... roles) { + List authorities = new ArrayList(roles.length); + for(String role : roles) { + if(this.rolePrefix == null || role.startsWith(this.rolePrefix)) { + authorities.add(new SimpleGrantedAuthority(role)); + } else { + authorities.add(new SimpleGrantedAuthority(this.rolePrefix + role)); + } + } + return this; + } + + /** + * Populates the user's {@link GrantedAuthority}'s. + * @param authorities + * @see #roles(String...) + */ + public UserRequestPostProcessor authorities(GrantedAuthority... authorities) { + this.authorities = Arrays.asList(authorities); + return this; + } + + public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(this.username, this.credentials, this.authorities); + save(authentication,request); + return request; + } + } + + public final static class UserDetailsRequestPostProcessor + extends SecurityContextRequestPostProcessorSupport implements RequestPostProcessor { + + private final String username; + + private String userDetailsServiceBeanId; + + private UserDetailsRequestPostProcessor(String username) { + this.username = username; + } + + /** + * Use this method to specify the bean id of the {@link UserDetailsService} to + * use to look up the {@link UserDetails}. + * + *

By default a lookup of {@link UserDetailsService} is performed by type. This + * can be problematic if multiple {@link UserDetailsService} beans are declared. + */ + public UserDetailsRequestPostProcessor userDetailsServiceBeanId(String userDetailsServiceBeanId) { + this.userDetailsServiceBeanId = userDetailsServiceBeanId; + return this; + } + + public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { + UsernamePasswordAuthenticationToken authentication = authentication(request.getServletContext()); + save(authentication,request); + return request; + } + + private UsernamePasswordAuthenticationToken authentication(ServletContext servletContext) { + ApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); + UserDetailsService userDetailsService = userDetailsService(context); + UserDetails userDetails = userDetailsService.loadUserByUsername(this.username); + return new UsernamePasswordAuthenticationToken( + userDetails, userDetails.getPassword(), userDetails.getAuthorities()); + } + + private UserDetailsService userDetailsService(ApplicationContext context) { + if(this.userDetailsServiceBeanId == null) { + return context.getBean(UserDetailsService.class); + } + return context.getBean(this.userDetailsServiceBeanId, UserDetailsService.class); + } + } + + private SecurityRequestPostProcessors() {} + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/SpringSecurityTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/SpringSecurityTests.java new file mode 100644 index 0000000000..e24bdba57d --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/SpringSecurityTests.java @@ -0,0 +1,135 @@ +/* + * 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.mock.servlet.samples.context; + +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.forwardedUrl; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.samples.context.SecurityRequestPostProcessors.user; +import static org.springframework.test.web.mock.servlet.samples.context.SecurityRequestPostProcessors.userDeatilsService; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.test.web.mock.servlet.MvcResult; +import org.springframework.test.web.mock.servlet.ResultMatcher; +import org.springframework.test.web.mock.servlet.request.RequestPostProcessor; +import org.springframework.test.web.mock.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +/** + * Basic example that includes Spring Security configuration. + * + *

Note that currently there are no {@link ResultMatcher}' built specifically + * for asserting the Spring Security context. However, it's quite easy to put + * them together as shown below and Spring Security extensions will become + * available in the near future. + * + *

This also demonstrates a custom {@link RequestPostProcessor} which authenticates + * a user to a particular {@link HttpServletRequest}. + * + *

Also see the Javadoc of {@link GenericWebContextLoader}, a class that + * provides temporary support for loading WebApplicationContext by extending + * the TestContext framework. + * + * @author Rob Winch + * @author Rossen Stoyanchev + * @see SecurityRequestPostProcessors + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration( + loader=WebContextLoader.class, + value={ + "classpath:org/springframework/test/web/mock/servlet/samples/context/security.xml", + "classpath:org/springframework/test/web/mock/servlet/samples/servlet-context.xml" + }) +public class SpringSecurityTests { + + private static String SEC_CONTEXT_ATTR = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY; + + @Autowired + private FilterChainProxy springSecurityFilterChain; + + @Autowired + private WebApplicationContext wac; + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac) + .addFilters(this.springSecurityFilterChain).build(); + } + + @Test + public void requiresAuthentication() throws Exception { + mockMvc.perform(get("/user")) + .andExpect(redirectedUrl("http://localhost/spring_security_login")); + } + + @Test + public void accessGranted() throws Exception { + this.mockMvc.perform(get("/").with(userDeatilsService("user"))) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp")); + } + + @Test + public void accessDenied() throws Exception { + this.mockMvc.perform(get("/").with(user("user").roles("DENIED"))) + .andExpect(status().isForbidden()); + } + + @Test + public void userAuthenticates() throws Exception { + final String username = "user"; + mockMvc.perform(post("/j_spring_security_check").param("j_username", username).param("j_password", "password")) + .andExpect(redirectedUrl("/")) + .andExpect(new ResultMatcher() { + public void match(MvcResult mvcResult) throws Exception { + HttpSession session = mvcResult.getRequest().getSession(); + SecurityContext securityContext = (SecurityContext) session.getAttribute(SEC_CONTEXT_ATTR); + Assert.assertEquals(securityContext.getAuthentication().getName(), username); + } + }); + } + + @Test + public void userAuthenticateFails() throws Exception { + final String username = "user"; + mockMvc.perform(post("/j_spring_security_check").param("j_username", username).param("j_password", "invalid")) + .andExpect(redirectedUrl("/spring_security_login?login_error")) + .andExpect(new ResultMatcher() { + public void match(MvcResult mvcResult) throws Exception { + HttpSession session = mvcResult.getRequest().getSession(); + SecurityContext securityContext = (SecurityContext) session.getAttribute(SEC_CONTEXT_ATTR); + Assert.assertNull(securityContext); + } + }); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/WebConfig.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/WebConfig.java new file mode 100644 index 0000000000..133e69fea6 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/WebConfig.java @@ -0,0 +1,62 @@ +/* + * 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.mock.servlet.samples.context; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; +import org.springframework.web.servlet.view.UrlBasedViewResolver; +import org.springframework.web.servlet.view.tiles2.TilesConfigurer; +import org.springframework.web.servlet.view.tiles2.TilesView; + +@Configuration +@EnableWebMvc +class WebConfig extends WebMvcConfigurerAdapter { + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/resources/**").addResourceLocations("/resources/"); + } + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/").setViewName("home"); + } + + @Override + public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { + configurer.enable(); + } + + @Bean + public UrlBasedViewResolver urlBasedViewResolver() { + UrlBasedViewResolver resolver = new UrlBasedViewResolver(); + resolver.setViewClass(TilesView.class); + return resolver; + } + + @Bean + public TilesConfigurer tilesConfigurer() { + TilesConfigurer configurer = new TilesConfigurer(); + configurer.setDefinitions(new String[] {"/WEB-INF/**/tiles.xml"}); + return configurer; + } +} \ No newline at end of file diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/WebContextLoader.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/WebContextLoader.java new file mode 100644 index 0000000000..7be5ee985c --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/WebContextLoader.java @@ -0,0 +1,24 @@ +/* + * 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.mock.servlet.samples.context; + +class WebContextLoader extends GenericWebContextLoader { + + public WebContextLoader() { + super("src/test/resources/META-INF/web-resources", false); + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/XmlTestContextTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/XmlTestContextTests.java new file mode 100644 index 0000000000..2edc313d03 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/context/XmlTestContextTests.java @@ -0,0 +1,66 @@ +/* + * 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.mock.servlet.samples.context; + +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.forwardedUrl; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.test.web.mock.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.ContextLoader; +import org.springframework.web.context.WebApplicationContext; + +/** + * Tests with XML configuration. + * + * The TestContext framework doesn't support WebApplicationContext yet: + * https://jira.springsource.org/browse/SPR-5243 + * + * A custom {@link ContextLoader} is used to load the WebApplicationContext. + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration( + loader=WebContextLoader.class, + locations={"/org/springframework/test/web/mock/servlet/samples/servlet-context.xml"}) +public class XmlTestContextTests { + + @Autowired + private WebApplicationContext wac; + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + } + + @Test + public void tilesDefinitions() throws Exception { + this.mockMvc.perform(get("/")) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("/WEB-INF/layouts/standardLayout.jsp")); + } + +} + diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/AsyncTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/AsyncTests.java new file mode 100644 index 0000000000..539c9474a6 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/AsyncTests.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.test.web.mock.servlet.samples.standalone; + +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.request; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import java.util.concurrent.Callable; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.context.request.async.DeferredResult; + +/** + * Tests with asynchronous request handling. + * + * @author Rossen Stoyanchev + */ +public class AsyncTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = standaloneSetup(new AsyncController()).build(); + } + + @Test + public void testDeferredResult() throws Exception { + this.mockMvc.perform(get("/1").param("deferredResult", "true")) + .andExpect(status().isOk()) + .andExpect(request().asyncStarted()); + } + + @Test + public void testCallable() throws Exception { + this.mockMvc.perform(get("/1").param("callable", "true")) + .andExpect(status().isOk()) + .andExpect(request().asyncStarted()) + .andExpect(request().asyncResult(new Person("Joe"))); + } + + + @Controller + private static class AsyncController { + + @RequestMapping(value="/{id}", params="deferredResult", produces="application/json") + public DeferredResult getDeferredResult() { + return new DeferredResult(); + } + + @RequestMapping(value="/{id}", params="callable", produces="application/json") + public Callable getCallable() { + return new Callable() { + public Person call() throws Exception { + Thread.sleep(100); + return new Person("Joe"); + } + }; + } + + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/ExceptionHandlerTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/ExceptionHandlerTests.java new file mode 100644 index 0000000000..e04a0481e4 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/ExceptionHandlerTests.java @@ -0,0 +1,63 @@ +/* + * 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.mock.servlet.samples.standalone; + +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.forwardedUrl; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Exception handling via {@code @ExceptionHandler} method. + * + * @author Rossen Stoyanchev + */ +public class ExceptionHandlerTests { + + @Test + public void testExceptionHandlerMethod() throws Exception { + standaloneSetup(new PersonController()).build() + .perform(get("/person/Clyde")) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("errorView")); + } + + + @Controller + private static class PersonController { + + @RequestMapping(value="/person/{name}", method=RequestMethod.GET) + public String show(@PathVariable String name) { + if (name.equals("Clyde")) { + throw new IllegalArgumentException("Black listed"); + } + return "person/show"; + } + + @ExceptionHandler + public String handleException(IllegalArgumentException exception) { + return "errorView"; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/FilterTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/FilterTests.java new file mode 100644 index 0000000000..d835f1b830 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/FilterTests.java @@ -0,0 +1,171 @@ +/* + * 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.mock.servlet.samples.standalone; + +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.flash; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import java.io.IOException; +import java.security.Principal; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpServletResponseWrapper; +import javax.validation.Valid; + +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.Person; +import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +/** + * Tests with {@link Filter}'s. + * + * @author Rob Winch + */ +public class FilterTests { + + @Test + public void whenFiltersCompleteMvcProcessesRequest() throws Exception { + standaloneSetup(new PersonController()) + .addFilters(new ContinueFilter()).build() + .perform(post("/persons").param("name", "Andy")) + .andExpect(status().isMovedTemporarily()) + .andExpect(redirectedUrl("/person/1")) + .andExpect(model().size(1)) + .andExpect(model().attributeExists("id")) + .andExpect(flash().attributeCount(1)) + .andExpect(flash().attribute("message", "success!")); + } + + @Test + public void filtersProcessRequest() throws Exception { + standaloneSetup(new PersonController()) + .addFilters(new ContinueFilter(), new RedirectFilter()).build() + .perform(post("/persons").param("name", "Andy")) + .andExpect(redirectedUrl("/login")); + } + + @Test + public void filterMappedBySuffix() throws Exception { + standaloneSetup(new PersonController()) + .addFilter(new RedirectFilter(), "*.html").build() + .perform(post("/persons.html").param("name", "Andy")) + .andExpect(redirectedUrl("/login")); + } + + @Test + public void filterWithExactMapping() throws Exception { + standaloneSetup(new PersonController()) + .addFilter(new RedirectFilter(), "/p", "/persons").build() + .perform(post("/persons").param("name", "Andy")) + .andExpect(redirectedUrl("/login")); + } + + @Test + public void filterSkipped() throws Exception { + standaloneSetup(new PersonController()) + .addFilter(new RedirectFilter(), "/p", "/person").build() + .perform(post("/persons").param("name", "Andy")) + .andExpect(status().isMovedTemporarily()) + .andExpect(redirectedUrl("/person/1")) + .andExpect(model().size(1)) + .andExpect(model().attributeExists("id")) + .andExpect(flash().attributeCount(1)) + .andExpect(flash().attribute("message", "success!")); + } + + @Test + public void filterWrapsRequestResponse() throws Exception { + standaloneSetup(new PersonController()) + .addFilters(new WrappingRequestResponseFilter()).build() + .perform(post("/user")) + .andExpect(model().attribute("principal", WrappingRequestResponseFilter.PRINCIPAL_NAME)); + } + + + @Controller + private static class PersonController { + @RequestMapping(value="/persons", method=RequestMethod.POST) + public String save(@Valid Person person, Errors errors, RedirectAttributes redirectAttrs) { + if (errors.hasErrors()) { + return "person/add"; + } + redirectAttrs.addAttribute("id", "1"); + redirectAttrs.addFlashAttribute("message", "success!"); + return "redirect:/person/{id}"; + } + + @RequestMapping(value="/user") + public ModelAndView user(Principal principal) { + return new ModelAndView("user/view", "principal", principal.getName()); + } + + @RequestMapping(value="/forward") + public String forward() { + return "forward:/persons"; + } + } + + private class ContinueFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + filterChain.doFilter(request, response); + } + } + + private static class WrappingRequestResponseFilter extends OncePerRequestFilter { + + public static final String PRINCIPAL_NAME = "WrapRequestResponseFilterPrincipal"; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + filterChain.doFilter(new HttpServletRequestWrapper(request) { + @Override + public Principal getUserPrincipal() { + return new Principal() { + public String getName() { + return PRINCIPAL_NAME; + } + }; + } + }, new HttpServletResponseWrapper(response)); + } + } + + private class RedirectFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + + response.sendRedirect("/login"); + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/RedirectTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/RedirectTests.java new file mode 100644 index 0000000000..0ec649ce9c --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/RedirectTests.java @@ -0,0 +1,103 @@ +/* + * 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.mock.servlet.samples.standalone; + +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import javax.validation.Valid; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.ui.Model; +import org.springframework.validation.Errors; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +/** + * Redirect scenarios including saving and retrieving flash attributes. + * + * @author Rossen Stoyanchev + */ +public class RedirectTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = standaloneSetup(new PersonController()).build(); + } + + @Test + public void save() throws Exception { + this.mockMvc.perform(post("/persons").param("name", "Andy")) + .andExpect(status().isMovedTemporarily()) + .andExpect(redirectedUrl("/persons/Joe")) + .andExpect(model().size(1)) + .andExpect(model().attributeExists("name")) + .andExpect(flash().attributeCount(1)) + .andExpect(flash().attribute("message", "success!")); + } + + @Test + public void saveWithErrors() throws Exception { + this.mockMvc.perform(post("/persons")) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("persons/add")) + .andExpect(model().size(1)) + .andExpect(model().attributeExists("person")) + .andExpect(flash().attributeCount(0)); + } + + @Test + public void getPerson() throws Exception { + this.mockMvc.perform(get("/persons/Joe").flashAttr("message", "success!")) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("persons/index")) + .andExpect(model().size(2)) + .andExpect(model().attribute("person", new Person("Joe"))) + .andExpect(model().attribute("message", "success!")) + .andExpect(flash().attributeCount(0)); + } + + + @Controller + private static class PersonController { + + @RequestMapping(value="/persons/{name}", method=RequestMethod.GET) + public String getPerson(@PathVariable String name, Model model) { + model.addAttribute(new Person(name)); + return "persons/index"; + } + + @RequestMapping(value="/persons", method=RequestMethod.POST) + public String save(@Valid Person person, Errors errors, RedirectAttributes redirectAttrs) { + if (errors.hasErrors()) { + return "persons/add"; + } + redirectAttrs.addAttribute("name", "Joe"); + redirectAttrs.addFlashAttribute("message", "success!"); + return "redirect:/persons/{name}"; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/RequestBuilderTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/RequestBuilderTests.java new file mode 100644 index 0000000000..f659480c2d --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/RequestBuilderTests.java @@ -0,0 +1,110 @@ +/* + * 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.mock.servlet.samples.standalone; + +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.test.web.mock.servlet.request.RequestPostProcessor; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * Demonstrates how to implement and plug in a custom {@link RequestPostProcessor}. + * + * @author Rossen Stoyanchev + */ +public class RequestBuilderTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = standaloneSetup(new SampleController()) + .defaultRequest(get("/").accept(MediaType.TEXT_PLAIN)) + .alwaysExpect(status().isOk()).build(); + } + + @Test + public void fooHeader() throws Exception { + this.mockMvc.perform(get("/").with(headers().foo("a=b"))).andExpect( + content().string("Foo")); + } + + @Test + public void barHeader() throws Exception { + this.mockMvc.perform(get("/").with(headers().bar("a=b"))).andExpect( + content().string("Bar")); + } + + private static HeaderRequestPostProcessor headers() { + return new HeaderRequestPostProcessor(); + } + + + /** + * Implementation of {@code RequestPostProcessor} with additional request + * building methods. + */ + private static class HeaderRequestPostProcessor implements RequestPostProcessor { + + private HttpHeaders headers = new HttpHeaders(); + + public HeaderRequestPostProcessor foo(String value) { + this.headers.add("Foo", value); + return this; + } + + public HeaderRequestPostProcessor bar(String value) { + this.headers.add("Bar", value); + return this; + } + + public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { + for (String headerName : this.headers.keySet()) { + request.addHeader(headerName, this.headers.get(headerName)); + } + return request; + } + } + + @Controller + @RequestMapping("/") + private static class SampleController { + + @RequestMapping(headers = "Foo") + @ResponseBody + public String handleFoo() { + return "Foo"; + } + + @RequestMapping(headers = "Bar") + @ResponseBody + public String handleBar() { + return "Bar"; + } + } + +} \ No newline at end of file diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/RequestParameterTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/RequestParameterTests.java new file mode 100644 index 0000000000..3780c7bd1d --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/RequestParameterTests.java @@ -0,0 +1,61 @@ +/* + * 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.mock.servlet.samples.standalone; + +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.Person; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * Tests demonstrating the use of request parameters. + * + * @author Rossen Stoyanchev + */ +public class RequestParameterTests { + + @Test + public void queryParameter() throws Exception { + + standaloneSetup(new PersonController()).build() + .perform(get("/search?name=George").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().mimeType("application/json;charset=UTF-8")) + .andExpect(jsonPath("$.name").value("George")); + } + + + @Controller + private class PersonController { + + @RequestMapping(value="/search") + @ResponseBody + public Person get(@RequestParam String name) { + Person person = new Person(name); + return person; + } + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/ResponseBodyTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/ResponseBodyTests.java new file mode 100644 index 0000000000..b101761616 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/ResponseBodyTests.java @@ -0,0 +1,59 @@ +/* + * 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.mock.servlet.samples.standalone; + +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.Person; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * Response written from {@code @ResponseBody} method. + * + * @author Rossen Stoyanchev + */ +public class ResponseBodyTests { + + @Test + public void json() throws Exception { + + standaloneSetup(new PersonController()).build() + .perform(get("/person/Lee").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().mimeType("application/json;charset=UTF-8")) + .andExpect(jsonPath("$.name").value("Lee")); + } + + @Controller + private class PersonController { + + @RequestMapping(value="/person/{name}") + @ResponseBody + public Person get(@PathVariable String name) { + Person person = new Person(name); + return person; + } + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/ViewResolutionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/ViewResolutionTests.java new file mode 100644 index 0000000000..eefbcac206 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/ViewResolutionTests.java @@ -0,0 +1,160 @@ +/* + * 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.mock.servlet.samples.standalone; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasProperty; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.forwardedUrl; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.model; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.xpath; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.oxm.jaxb.Jaxb2Marshaller; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.ui.Model; +import org.springframework.web.accept.ContentNegotiationManager; +import org.springframework.web.accept.FixedContentNegotiationStrategy; +import org.springframework.web.accept.HeaderContentNegotiationStrategy; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.View; +import org.springframework.web.servlet.view.ContentNegotiatingViewResolver; +import org.springframework.web.servlet.view.InternalResourceViewResolver; +import org.springframework.web.servlet.view.json.MappingJacksonJsonView; +import org.springframework.web.servlet.view.xml.MarshallingView; + +/** + * Tests with view resolution. + * + * @author Rossen Stoyanchev + */ +public class ViewResolutionTests { + + @Test + public void testJspOnly() throws Exception { + + InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); + viewResolver.setPrefix("/WEB-INF/"); + viewResolver.setSuffix(".jsp"); + + standaloneSetup(new PersonController()).setViewResolvers(viewResolver).build() + .perform(get("/person/Corea")) + .andExpect(status().isOk()) + .andExpect(model().size(1)) + .andExpect(model().attributeExists("person")) + .andExpect(forwardedUrl("/WEB-INF/person/show.jsp")); + } + + @Test + public void testJsonOnly() throws Exception { + + standaloneSetup(new PersonController()).setSingleView(new MappingJacksonJsonView()).build() + .perform(get("/person/Corea")) + .andExpect(status().isOk()) + .andExpect(content().mimeType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.person.name").value("Corea")); + } + + @Test + public void testXmlOnly() throws Exception { + + Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); + marshaller.setClassesToBeBound(Person.class); + + standaloneSetup(new PersonController()).setSingleView(new MarshallingView(marshaller)).build() + .perform(get("/person/Corea")) + .andExpect(status().isOk()) + .andExpect(content().mimeType(MediaType.APPLICATION_XML)) + .andExpect(xpath("/person/name/text()").string(equalTo("Corea"))); + } + + @Test + public void testContentNegotiation() throws Exception { + + Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); + marshaller.setClassesToBeBound(Person.class); + + List viewList = new ArrayList(); + viewList.add(new MappingJacksonJsonView()); + viewList.add(new MarshallingView(marshaller)); + + ContentNegotiationManager manager = new ContentNegotiationManager( + new HeaderContentNegotiationStrategy(), new FixedContentNegotiationStrategy(MediaType.TEXT_HTML)); + + ContentNegotiatingViewResolver cnViewResolver = new ContentNegotiatingViewResolver(); + cnViewResolver.setDefaultViews(viewList); + cnViewResolver.setContentNegotiationManager(manager); + cnViewResolver.afterPropertiesSet(); + + MockMvc mockMvc = + standaloneSetup(new PersonController()) + .setViewResolvers(cnViewResolver, new InternalResourceViewResolver()) + .build(); + + mockMvc.perform(get("/person/Corea")) + .andExpect(status().isOk()) + .andExpect(model().size(1)) + .andExpect(model().attributeExists("person")) + .andExpect(forwardedUrl("person/show")); + + mockMvc.perform(get("/person/Corea").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().mimeType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$.person.name").value("Corea")); + + mockMvc.perform(get("/person/Corea").accept(MediaType.APPLICATION_XML)) + .andExpect(status().isOk()) + .andExpect(content().mimeType(MediaType.APPLICATION_XML)) + .andExpect(xpath("/person/name/text()").string(equalTo("Corea"))); + } + + @Test + public void defaultViewResolver() throws Exception { + + standaloneSetup(new PersonController()).build() + .perform(get("/person/Corea")) + .andExpect(model().attribute("person", hasProperty("name", equalTo("Corea")))) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("person/show")); // InternalResourceViewResolver + } + + + @Controller + private static class PersonController { + + @RequestMapping(value="/person/{name}", method=RequestMethod.GET) + public String show(@PathVariable String name, Model model) { + Person person = new Person(name); + model.addAttribute(person); + return "person/show"; + } + } + +} + diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resulthandlers/PrintingResultHandlerTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resulthandlers/PrintingResultHandlerTests.java new file mode 100644 index 0000000000..c0d3adedc2 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resulthandlers/PrintingResultHandlerTests.java @@ -0,0 +1,49 @@ +/* + * 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.mock.servlet.samples.standalone.resulthandlers; + +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * Print debugging information about the executed request and response to System.out. + * + * @author Rossen Stoyanchev + */ +public class PrintingResultHandlerTests { + + @Test + public void testPrint() throws Exception { + + // Not testing anything, uncomment to see the output + + // standaloneSetup(new SimpleController()).build().perform(get("/")).andDo(print()); + } + + + @Controller + private static class SimpleController { + + @RequestMapping("/") + @ResponseBody + public String hello() { + return "Hello world"; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java new file mode 100644 index 0000000000..013353186b --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/ContentAssertionTests.java @@ -0,0 +1,106 @@ +/* + * 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.mock.servlet.samples.standalone.resultmatchers; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * Examples of defining expectations on the response content, content type, and + * the character encoding. + * + * @author Rossen Stoyanchev + * + * @see JsonPathAssertionTests + * @see XmlContentAssertionTests + * @see XpathAssertionTests + */ +public class ContentAssertionTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = standaloneSetup(new SimpleController()).alwaysExpect(status().isOk()).build(); + } + + @Test + public void testContentType() throws Exception { + this.mockMvc.perform(get("/handle")) + .andExpect(content().mimeType(MediaType.TEXT_PLAIN)) + .andExpect(content().mimeType("text/plain")); + + this.mockMvc.perform(get("/handleUtf8")) + .andExpect(content().mimeType(MediaType.valueOf("text/plain;charset=UTF-8"))) + .andExpect(content().mimeType("text/plain;charset=UTF-8")); + } + + @Test + public void testContentAsString() throws Exception { + this.mockMvc.perform(get("/handle")).andExpect(content().string("Hello world!")); + this.mockMvc.perform(get("/handleUtf8")).andExpect(content().string("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01")); + + // Hamcrest matchers... + this.mockMvc.perform(get("/handle")).andExpect(content().string(equalTo("Hello world!"))); + this.mockMvc.perform(get("/handleUtf8")).andExpect(content().string(equalTo("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01"))); + } + + @Test + public void testContentAsBytes() throws Exception { + this.mockMvc.perform(get("/handle")).andExpect(content().bytes("Hello world!".getBytes("ISO-8859-1"))); + this.mockMvc.perform(get("/handleUtf8")).andExpect(content().bytes("\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01".getBytes("UTF-8"))); + } + + @Test + public void testContentStringMatcher() throws Exception { + this.mockMvc.perform(get("/handle")).andExpect(content().string(containsString("world"))); + } + + @Test + public void testCharacterEncoding() throws Exception { + this.mockMvc.perform(get("/handle")).andExpect(content().encoding("ISO-8859-1")); + this.mockMvc.perform(get("/handleUtf8")).andExpect(content().encoding("UTF-8")); + } + + + @Controller + private static class SimpleController { + + @RequestMapping(value="/handle", produces="text/plain") + @ResponseBody + public String handle() { + return "Hello world!"; + } + + @RequestMapping(value="/handleUtf8", produces="text/plain;charset=UTF-8") + @ResponseBody + public String handleWithCharset() { + return "\u3053\u3093\u306b\u3061\u306f\u4e16\u754c\uff01"; // "Hello world! (Japanese) + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/CookieAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/CookieAssertionTests.java new file mode 100644 index 0000000000..0a6dcf1b4d --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/CookieAssertionTests.java @@ -0,0 +1,114 @@ +/* + * 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.mock.servlet.samples.standalone.resultmatchers; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.startsWith; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.cookie; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.i18n.CookieLocaleResolver; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; + +/** + * Examples of expectations on response cookies values. + * + * @author Rossen Stoyanchev + */ +public class CookieAssertionTests { + + private static final String COOKIE_NAME = CookieLocaleResolver.DEFAULT_COOKIE_NAME; + + private MockMvc mockMvc; + + + @Before + public void setup() { + CookieLocaleResolver localeResolver = new CookieLocaleResolver(); + localeResolver.setCookieDomain("domain"); + + this.mockMvc = standaloneSetup(new SimpleController()) + .addInterceptors(new LocaleChangeInterceptor()) + .setLocaleResolver(localeResolver) + .defaultRequest(get("/").param("locale", "en_US")) + .alwaysExpect(status().isOk()) + .build(); + } + + @Test + public void testExists() throws Exception { + this.mockMvc.perform(get("/")).andExpect(cookie().exists(COOKIE_NAME)); + } + + @Test + public void testNotExists() throws Exception { + this.mockMvc.perform(get("/")).andExpect(cookie().doesNotExist("unknowCookie")); + } + + @Test + public void testEqualTo() throws Exception { + this.mockMvc.perform(get("/")).andExpect(cookie().value(COOKIE_NAME, "en_US")); + this.mockMvc.perform(get("/")).andExpect(cookie().value(COOKIE_NAME, equalTo("en_US"))); + } + + @Test + public void testMatcher() throws Exception { + this.mockMvc.perform(get("/")).andExpect(cookie().value(COOKIE_NAME, startsWith("en"))); + } + + @Test + public void testMaxAge() throws Exception { + this.mockMvc.perform(get("/")).andExpect(cookie().maxAge(COOKIE_NAME, -1)); + } + + @Test + public void testDomain() throws Exception { + this.mockMvc.perform(get("/")).andExpect(cookie().domain(COOKIE_NAME, "domain")); + } + + @Test + public void testVersion() throws Exception { + this.mockMvc.perform(get("/")).andExpect(cookie().version(COOKIE_NAME, 0)); + } + + @Test + public void testPath() throws Exception { + this.mockMvc.perform(get("/")).andExpect(cookie().path(COOKIE_NAME, "/")); + } + + @Test + public void testSecured() throws Exception { + this.mockMvc.perform(get("/")).andExpect(cookie().secure(COOKIE_NAME, false)); + } + + + @Controller + private static class SimpleController { + + @RequestMapping("/") + public String home() { + return "home"; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/FlashAttributeAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/FlashAttributeAssertionTests.java new file mode 100644 index 0000000000..dce7a4d308 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/FlashAttributeAssertionTests.java @@ -0,0 +1,93 @@ +/* + * 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.mock.servlet.samples.standalone.resultmatchers; + +import static org.hamcrest.Matchers.closeTo; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.flash; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import java.net.URL; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +/** + * Examples of expectations on flash attributes. + * + * @author Rossen Stoyanchev + */ +public class FlashAttributeAssertionTests { + + private MockMvc mockMvc; + + + @Before + public void setup() { + this.mockMvc = standaloneSetup(new PersonController()) + .alwaysExpect(status().isMovedTemporarily()) + .alwaysExpect(flash().attributeCount(3)) + .build(); + } + + @Test + public void testExists() throws Exception { + this.mockMvc.perform(post("/persons")) + .andExpect(flash().attributeExists("one", "two", "three")); + } + + @Test + public void testEqualTo() throws Exception { + this.mockMvc.perform(post("/persons")) + .andExpect(flash().attribute("one", "1")) + .andExpect(flash().attribute("two", 2.222)) + .andExpect(flash().attribute("three", new URL("http://example.com"))) + .andExpect(flash().attribute("one", equalTo("1"))) // Hamcrest... + .andExpect(flash().attribute("two", equalTo(2.222))) + .andExpect(flash().attribute("three", equalTo(new URL("http://example.com")))); + } + + @Test + public void testMatchers() throws Exception { + this.mockMvc.perform(post("/persons")) + .andExpect(flash().attribute("one", containsString("1"))) + .andExpect(flash().attribute("two", closeTo(2, 0.5))) + .andExpect(flash().attribute("three", notNullValue())); + } + + + @Controller + private static class PersonController { + + @RequestMapping(value="/persons", method=RequestMethod.POST) + public String save(RedirectAttributes redirectAttrs) throws Exception { + redirectAttrs.addFlashAttribute("one", "1"); + redirectAttrs.addFlashAttribute("two", 2.222); + redirectAttrs.addFlashAttribute("three", new URL("http://example.com")); + return "redirect:/person/1"; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/HandlerAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/HandlerAssertionTests.java new file mode 100644 index 0000000000..536d59e40d --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/HandlerAssertionTests.java @@ -0,0 +1,85 @@ +/* + * 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.mock.servlet.samples.standalone.resultmatchers; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.handler; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import java.lang.reflect.Method; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * Examples of expectations on the handler or handler method that executed the request. + * + *

Note that in most cases "handler" is synonymous with "controller". + * For example an {@code @Controller} is a kind of handler. + * + * @author Rossen Stoyanchev + */ +public class HandlerAssertionTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = standaloneSetup(new SimpleController()).alwaysExpect(status().isOk()).build(); + } + + @Test + public void testHandlerType() throws Exception { + this.mockMvc.perform(get("/")).andExpect(handler().handlerType(SimpleController.class)); + } + + @Test + public void testHandlerMethodNameEqualTo() throws Exception { + this.mockMvc.perform(get("/")).andExpect(handler().methodName("handle")); + + // Hamcrest matcher.. + this.mockMvc.perform(get("/")).andExpect(handler().methodName(equalTo("handle"))); + } + + @Test + public void testHandlerMethodNameMatcher() throws Exception { + this.mockMvc.perform(get("/")).andExpect(handler().methodName(is(not("save")))); + } + + @Test + public void testHandlerMethod() throws Exception { + Method method = SimpleController.class.getMethod("handle"); + this.mockMvc.perform(get("/")).andExpect(handler().method(method)); + } + + + @Controller + private static class SimpleController { + + @RequestMapping("/") + public String handle() { + return "view"; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java new file mode 100644 index 0000000000..2da15c5c35 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/HeaderAssertionTests.java @@ -0,0 +1,102 @@ +/* + * 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.mock.servlet.samples.standalone.resultmatchers; + +import static org.hamcrest.Matchers.nullValue; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import java.util.Date; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.context.request.WebRequest; + +/** + * Examples of expectations on response header values. + * + * @author Rossen Stoyanchev + */ +public class HeaderAssertionTests { + + private MockMvc mockMvc; + + private PersonController personController; + + @Before + public void setup() { + this.personController = new PersonController(); + this.mockMvc = standaloneSetup(this.personController).build(); + } + + @Test + public void testValue() throws Exception { + long currentTime = new Date().getTime(); + this.personController.setStubTimestamp(currentTime); + this.mockMvc.perform(get("/persons/1").header("If-Modified-Since", currentTime - (1000 * 60))) + .andExpect(header().string("Last-Modified", String.valueOf(currentTime))); + } + + @Test + public void testLongValue() throws Exception { + long currentTime = new Date().getTime(); + this.personController.setStubTimestamp(currentTime); + this.mockMvc.perform(get("/persons/1").header("If-Modified-Since", currentTime - (1000 * 60))) + .andExpect(header().longValue("Last-Modified", currentTime)); + } + + @Test + public void testMatcher() throws Exception { + long currentTime = new Date().getTime(); + this.personController.setStubTimestamp(currentTime); + this.mockMvc.perform(get("/persons/1").header("If-Modified-Since", currentTime)) + .andExpect(status().isNotModified()) + .andExpect(header().string("Last-Modified", nullValue())); + } + + + @Controller + private static class PersonController { + + private long timestamp; + + public void setStubTimestamp(long timestamp) { + this.timestamp = timestamp; + } + + @RequestMapping("/persons/{id}") + @ResponseBody + public Person showEntity(@PathVariable long id, WebRequest request) { + if (request.checkNotModified(calculateLastModified(id))) { + return null; + } + return new Person("Jason"); + } + + private long calculateLastModified(long id) { + return this.timestamp; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/JsonPathAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/JsonPathAssertionTests.java new file mode 100644 index 0000000000..af6ff71ae4 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/JsonPathAssertionTests.java @@ -0,0 +1,146 @@ +/* + * 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.mock.servlet.samples.standalone.resultmatchers; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.isIn; +import static org.hamcrest.Matchers.startsWith; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * Examples of defining expectations on JSON response content with + * JSONPath expressions. + * + * @author Rossen Stoyanchev + * + * @see ContentAssertionTests + */ +public class JsonPathAssertionTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = standaloneSetup(new MusicController()) + .defaultRequest(get("/").accept(MediaType.APPLICATION_JSON)) + .alwaysExpect(status().isOk()) + .alwaysExpect(content().mimeType("application/json;charset=UTF-8")) + .build(); + } + + @Test + public void testExists() throws Exception { + + String composerByName = "$.composers[?(@.name == '%s')]"; + String performerByName = "$.performers[?(@.name == '%s')]"; + + this.mockMvc.perform(get("/music/people")) + .andExpect(jsonPath(composerByName, "Johann Sebastian Bach").exists()) + .andExpect(jsonPath(composerByName, "Johannes Brahms").exists()) + .andExpect(jsonPath(composerByName, "Edvard Grieg").exists()) + .andExpect(jsonPath(composerByName, "Robert Schumann").exists()) + .andExpect(jsonPath(performerByName, "Vladimir Ashkenazy").exists()) + .andExpect(jsonPath(performerByName, "Yehudi Menuhin").exists()) + .andExpect(jsonPath("$.composers[0]").exists()) + .andExpect(jsonPath("$.composers[1]").exists()) + .andExpect(jsonPath("$.composers[2]").exists()) + .andExpect(jsonPath("$.composers[3]").exists()); + + } + + @Test + public void testDoesNotExist() throws Exception { + this.mockMvc.perform(get("/music/people")) + .andExpect(jsonPath("$.composers[?(@.name == 'Edvard Grieeeeeeg')]").doesNotExist()) + .andExpect(jsonPath("$.composers[?(@.name == 'Robert Schuuuuuuman')]").doesNotExist()) + .andExpect(jsonPath("$.composers[-1]").doesNotExist()) + .andExpect(jsonPath("$.composers[4]").doesNotExist()); + } + + @Test + public void testEqualTo() throws Exception { + this.mockMvc.perform(get("/music/people")) + .andExpect(jsonPath("$.composers[0].name").value("Johann Sebastian Bach")) + .andExpect(jsonPath("$.performers[1].name").value("Yehudi Menuhin")); + + // Hamcrest matchers... + this.mockMvc.perform(get("/music/people")) + .andExpect(jsonPath("$.composers[0].name").value(equalTo("Johann Sebastian Bach"))) + .andExpect(jsonPath("$.performers[1].name").value(equalTo("Yehudi Menuhin"))); + } + + @Test + public void testHamcrestMatcher() throws Exception { + this.mockMvc.perform(get("/music/people")) + .andExpect(jsonPath("$.composers[0].name", startsWith("Johann"))) + .andExpect(jsonPath("$.performers[0].name", endsWith("Ashkenazy"))) + .andExpect(jsonPath("$.performers[1].name", containsString("di Me"))) + .andExpect(jsonPath("$.composers[1].name", isIn(Arrays.asList("Johann Sebastian Bach", "Johannes Brahms")))); + } + + @Test + public void testHamcrestMatcherWithParameterizedJsonPath() throws Exception { + + String composerName = "$.composers[%s].name"; + String performerName = "$.performers[%s].name"; + + this.mockMvc.perform(get("/music/people")) + .andExpect(jsonPath(composerName, 0).value(startsWith("Johann"))) + .andExpect(jsonPath(performerName, 0).value(endsWith("Ashkenazy"))) + .andExpect(jsonPath(performerName, 1).value(containsString("di Me"))) + .andExpect(jsonPath(composerName, 1).value(isIn(Arrays.asList("Johann Sebastian Bach", "Johannes Brahms")))); + } + + + @Controller + private class MusicController { + + @RequestMapping(value="/music/people") + public @ResponseBody MultiValueMap get() { + MultiValueMap map = new LinkedMultiValueMap(); + + map.add("composers", new Person("Johann Sebastian Bach")); + map.add("composers", new Person("Johannes Brahms")); + map.add("composers", new Person("Edvard Grieg")); + map.add("composers", new Person("Robert Schumann")); + + map.add("performers", new Person("Vladimir Ashkenazy")); + map.add("performers", new Person("Yehudi Menuhin")); + + return map; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/ModelAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/ModelAssertionTests.java new file mode 100644 index 0000000000..01afee6e38 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/ModelAssertionTests.java @@ -0,0 +1,124 @@ +/* + * 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.mock.servlet.samples.standalone.resultmatchers; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.hasProperty; +import static org.hamcrest.Matchers.lessThan; +import static org.hamcrest.Matchers.notNullValue; +import static org.hamcrest.Matchers.nullValue; +import static org.hamcrest.Matchers.startsWith; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import javax.validation.Valid; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.ui.Model; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +/** + * Examples of expectations on the content of the model prepared by the controller. + * + * @author Rossen Stoyanchev + */ +public class ModelAssertionTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + + SampleController controller = new SampleController("a string value", 3, new Person("a name")); + + this.mockMvc = standaloneSetup(controller) + .defaultRequest(get("/")) + .alwaysExpect(status().isOk()) + .build(); + } + + @Test + public void testAttributeEqualTo() throws Exception { + mockMvc.perform(get("/")) + .andExpect(model().attribute("integer", 3)) + .andExpect(model().attribute("string", "a string value")) + .andExpect(model().attribute("integer", equalTo(3))) // Hamcrest... + .andExpect(model().attribute("string", equalTo("a string value"))); + } + + @Test + public void testAttributeExists() throws Exception { + mockMvc.perform(get("/")) + .andExpect(model().attributeExists("integer", "string", "person")) + .andExpect(model().attribute("integer", notNullValue())) // Hamcrest... + .andExpect(model().attribute("INTEGER", nullValue())); + } + + @SuppressWarnings("unchecked") + @Test + public void testAttributeHamcrestMatchers() throws Exception { + mockMvc.perform(get("/")) + .andExpect(model().attribute("integer", allOf(greaterThan(2), lessThan(4)))) + .andExpect(model().attribute("string", allOf(startsWith("a string"), endsWith("value")))) + .andExpect(model().attribute("person", hasProperty("name", equalTo("a name")))); + } + + @Test + public void testHasErrors() throws Exception { + mockMvc.perform(post("/persons")).andExpect(model().attributeHasErrors("person")); + } + + @Test + public void testHasNoErrors() throws Exception { + mockMvc.perform(get("/")).andExpect(model().hasNoErrors()); + } + + + @Controller + private static class SampleController { + + private final Object[] values; + + public SampleController(Object... values) { + this.values = values; + } + + @RequestMapping("/") + public String handle(Model model) { + for (Object value : this.values) { + model.addAttribute(value); + } + return "view"; + } + + @RequestMapping(value="/persons", method=RequestMethod.POST) + public String create(@Valid Person person, BindingResult result, Model model) { + return "view"; + } + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/RequestAttributeAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/RequestAttributeAssertionTests.java new file mode 100644 index 0000000000..a94ea52bf5 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/RequestAttributeAssertionTests.java @@ -0,0 +1,77 @@ +/* + * 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.mock.servlet.samples.standalone.resultmatchers; + +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.request; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.servlet.HandlerMapping; + +/** + * Examples of expectations on created request attributes. + * + * @author Rossen Stoyanchev + */ +public class RequestAttributeAssertionTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = standaloneSetup(new SimpleController()).build(); + } + + @Test + public void testRequestAttributeEqualTo() throws Exception { + this.mockMvc.perform(get("/main/1").servletPath("/main")) + .andExpect(request().attribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, "/{id}")) + .andExpect(request().attribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/1")) + .andExpect(request().attribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE, equalTo("/{id}"))) + .andExpect(request().attribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, equalTo("/1"))); + } + + @Test + public void testRequestAttributeMatcher() throws Exception { + + String producibleMediaTypes = HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE; + + this.mockMvc.perform(get("/1")) + .andExpect(request().attribute(producibleMediaTypes, hasItem(MediaType.APPLICATION_JSON))) + .andExpect(request().attribute(producibleMediaTypes, not(hasItem(MediaType.APPLICATION_XML)))); + } + + + @Controller + private static class SimpleController { + + @RequestMapping(value="/{id}", produces="application/json") + public String show() { + return "view"; + } + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/SessionAttributeAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/SessionAttributeAssertionTests.java new file mode 100644 index 0000000000..2a55f99e63 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/SessionAttributeAssertionTests.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.test.web.mock.servlet.samples.standalone.resultmatchers; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.notNullValue; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.request; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import java.util.Locale; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.SessionAttributes; + +/** + * Examples of expectations on created session attributes. + * + * @author Rossen Stoyanchev + */ +public class SessionAttributeAssertionTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = standaloneSetup(new SimpleController()) + .defaultRequest(get("/")) + .alwaysExpect(status().isOk()) + .build(); + } + + @Test + public void testSessionAttributeEqualTo() throws Exception { + this.mockMvc.perform(get("/")) + .andExpect(request().sessionAttribute("locale", Locale.UK)) + .andExpect(request().sessionAttribute("locale", equalTo(Locale.UK))); + } + + @Test + public void testSessionAttributeMatcher() throws Exception { + this.mockMvc.perform(get("/")) + .andExpect(request().sessionAttribute("locale", notNullValue())); + } + + + @Controller + @SessionAttributes("locale") + private static class SimpleController { + + @ModelAttribute + public void populate(Model model) { + model.addAttribute("locale", Locale.UK); + } + + @RequestMapping("/") + public String handle() { + return "view"; + } + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/StatusAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/StatusAssertionTests.java new file mode 100644 index 0000000000..4a22c29563 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/StatusAssertionTests.java @@ -0,0 +1,103 @@ +/* + * 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.mock.servlet.samples.standalone.resultmatchers; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.endsWith; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; +import static org.hamcrest.Matchers.lessThan; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * Examples of expectations on the status and the status reason found in the response. + * + * @author Rossen Stoyanchev + */ +public class StatusAssertionTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = standaloneSetup(new StatusController()).build(); + } + + @Test + public void testStatusInt() throws Exception { + this.mockMvc.perform(get("/created")).andExpect(status().is(201)); + this.mockMvc.perform(get("/badRequest")).andExpect(status().is(400)); + } + + @Test + public void testHttpStatus() throws Exception { + this.mockMvc.perform(get("/created")).andExpect(status().isCreated()); + this.mockMvc.perform(get("/badRequest")).andExpect(status().isBadRequest()); + } + + @SuppressWarnings("unchecked") + @Test + public void testMatcher() throws Exception { + this.mockMvc.perform(get("/badRequest")) + .andExpect(status().is(allOf(greaterThanOrEqualTo(400), lessThan(500)))); + } + + @Test + public void testReasonEqualTo() throws Exception { + this.mockMvc.perform(get("/badRequest")).andExpect(status().reason("Expired token")); + + // Hamcrest matchers... + this.mockMvc.perform(get("/badRequest")).andExpect(status().reason(equalTo("Expired token"))); + } + + @Test + public void testReasonMatcher() throws Exception { + this.mockMvc.perform(get("/badRequest")) + .andExpect(status().reason(endsWith("token"))); + } + + + @Controller + private static class StatusController { + + @RequestMapping("/created") + @ResponseStatus(HttpStatus.CREATED) + public @ResponseBody void created(){ + } + + @RequestMapping("/badRequest") + @ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="Expired token") + public @ResponseBody void badRequest(){ + } + + @RequestMapping("/notImplemented") + @ResponseStatus(HttpStatus.NOT_IMPLEMENTED) + public @ResponseBody void notImplemented(){ + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/UrlAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/UrlAssertionTests.java new file mode 100644 index 0000000000..75c003875a --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/UrlAssertionTests.java @@ -0,0 +1,68 @@ +/* + * 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.mock.servlet.samples.standalone.resultmatchers; + +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.forwardedUrl; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * Examples of expectations on forwarded or redirected URLs. + * + * @author Rossen Stoyanchev + */ +public class UrlAssertionTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = standaloneSetup(new SimpleController()).build(); + } + + @Test + public void testRedirect() throws Exception { + this.mockMvc.perform(get("/persons")).andExpect(redirectedUrl("/persons/1")); + } + + @Test + public void testForward() throws Exception { + this.mockMvc.perform(get("/")).andExpect(forwardedUrl("/home")); + } + + + @Controller + private static class SimpleController { + + @RequestMapping("/persons") + public String save() { + return "redirect:/persons/1"; + } + + @RequestMapping("/") + public String forward() { + return "forward:/home"; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/ViewNameAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/ViewNameAssertionTests.java new file mode 100644 index 0000000000..deee90d77e --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/ViewNameAssertionTests.java @@ -0,0 +1,69 @@ +/* + * 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.mock.servlet.samples.standalone.resultmatchers; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.view; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * Examples of expectations on the view name selected by the controller. + * + * @author Rossen Stoyanchev + */ +public class ViewNameAssertionTests { + + private MockMvc mockMvc; + + @Before + public void setup() { + this.mockMvc = standaloneSetup(new SimpleController()) + .alwaysExpect(status().isOk()) + .build(); + } + + @Test + public void testEqualTo() throws Exception { + this.mockMvc.perform(get("/")) + .andExpect(view().name("mySpecialView")) + .andExpect(view().name(equalTo("mySpecialView"))); + } + + @Test + public void testHamcrestMatcher() throws Exception { + this.mockMvc.perform(get("/")).andExpect(view().name(containsString("Special"))); + } + + + @Controller + private static class SimpleController { + + @RequestMapping("/") + public String handle() { + return "mySpecialView"; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/XmlContentAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/XmlContentAssertionTests.java new file mode 100644 index 0000000000..302d1de763 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/XmlContentAssertionTests.java @@ -0,0 +1,123 @@ +/* + * 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.mock.servlet.samples.standalone.resultmatchers; + +import static org.hamcrest.Matchers.hasXPath; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import java.util.Arrays; +import java.util.List; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * Examples of defining expectations on XML response content with XMLUnit. + * + * @author Rossen Stoyanchev + * + * @see ContentAssertionTests + * @see XpathAssertionTests + */ +public class XmlContentAssertionTests { + + private static final String PEOPLE_XML = + "" + + "" + + "Johann Sebastian Bachfalse21.0" + + "Johannes Brahmsfalse0.0025" + + "Edvard Griegfalse1.6035" + + "Robert SchumannfalseNaN" + + ""; + + private MockMvc mockMvc; + + + @Before + public void setup() { + this.mockMvc = standaloneSetup(new MusicController()) + .defaultRequest(get("/").accept(MediaType.APPLICATION_XML)) + .alwaysExpect(status().isOk()) + .alwaysExpect(content().mimeType(MediaType.APPLICATION_XML)) + .build(); + } + + @Test + public void testXmlEqualTo() throws Exception { + this.mockMvc.perform(get("/music/people")).andExpect(content().xml(PEOPLE_XML)); + } + + @Test + public void testNodeHamcrestMatcher() throws Exception { + + this.mockMvc.perform(get("/music/people")) + .andExpect(content().node(hasXPath("/people/composers/composer[1]"))); + } + + + @Controller + private static class MusicController { + + @RequestMapping(value="/music/people") + public @ResponseBody PeopleWrapper getPeople() { + + List composers = Arrays.asList( + new Person("Johann Sebastian Bach").setSomeDouble(21), + new Person("Johannes Brahms").setSomeDouble(.0025), + new Person("Edvard Grieg").setSomeDouble(1.6035), + new Person("Robert Schumann").setSomeDouble(Double.NaN)); + + return new PeopleWrapper(composers); + } + } + + @SuppressWarnings("unused") + @XmlRootElement(name="people") + @XmlAccessorType(XmlAccessType.FIELD) + private static class PeopleWrapper { + + @XmlElementWrapper(name="composers") + @XmlElement(name="composer") + private List composers; + + public PeopleWrapper() { + } + + public PeopleWrapper(List composers) { + this.composers = composers; + } + + public List getComposers() { + return this.composers; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java new file mode 100644 index 0000000000..5175611c5b --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/samples/standalone/resultmatchers/XpathAssertionTests.java @@ -0,0 +1,202 @@ +/* + * 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.mock.servlet.samples.standalone.resultmatchers; + +import static org.hamcrest.Matchers.*; +import static org.springframework.test.web.mock.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.test.web.mock.servlet.result.MockMvcResultMatchers.xpath; +import static org.springframework.test.web.mock.servlet.setup.MockMvcBuilders.standaloneSetup; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlElementWrapper; +import javax.xml.bind.annotation.XmlRootElement; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.Person; +import org.springframework.test.web.mock.servlet.MockMvc; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +/** + * Examples of expectations on XML response content with XPath expressions. + * + * @author Rossen Stoyanchev + * + * @see ContentAssertionTests + * @see XmlContentAssertionTests + */ +public class XpathAssertionTests { + + private static final Map NS = + Collections.singletonMap("ns", "http://example.org/music/people"); + + private MockMvc mockMvc; + + @Before + public void setup() throws Exception { + this.mockMvc = standaloneSetup(new MusicController()) + .defaultRequest(get("/").accept(MediaType.APPLICATION_XML)) + .alwaysExpect(status().isOk()) + .alwaysExpect(content().mimeType(MediaType.APPLICATION_XML)) + .build(); + } + + @Test + public void testExists() throws Exception { + + String composer = "/ns:people/composers/composer[%s]"; + String performer = "/ns:people/performers/performer[%s]"; + + this.mockMvc.perform(get("/music/people")) + .andExpect(xpath(composer, NS, 1).exists()) + .andExpect(xpath(composer, NS, 2).exists()) + .andExpect(xpath(composer, NS, 3).exists()) + .andExpect(xpath(composer, NS, 4).exists()) + .andExpect(xpath(performer, NS, 1).exists()) + .andExpect(xpath(performer, NS, 2).exists()) + .andExpect(xpath(composer, NS, 1).node(notNullValue())); + } + + @Test + public void testDoesNotExist() throws Exception { + + String composer = "/ns:people/composers/composer[%s]"; + String performer = "/ns:people/performers/performer[%s]"; + + this.mockMvc.perform(get("/music/people")) + .andExpect(xpath(composer, NS, 0).doesNotExist()) + .andExpect(xpath(composer, NS, 5).doesNotExist()) + .andExpect(xpath(performer, NS, 0).doesNotExist()) + .andExpect(xpath(performer, NS, 3).doesNotExist()) + .andExpect(xpath(composer, NS, 0).node(nullValue())); + } + + @Test + public void testString() throws Exception { + + String composerName = "/ns:people/composers/composer[%s]/name"; + String performerName = "/ns:people/performers/performer[%s]/name"; + + this.mockMvc.perform(get("/music/people")) + .andExpect(xpath(composerName, NS, 1).string("Johann Sebastian Bach")) + .andExpect(xpath(composerName, NS, 2).string("Johannes Brahms")) + .andExpect(xpath(composerName, NS, 3).string("Edvard Grieg")) + .andExpect(xpath(composerName, NS, 4).string("Robert Schumann")) + .andExpect(xpath(performerName, NS, 1).string("Vladimir Ashkenazy")) + .andExpect(xpath(performerName, NS, 2).string("Yehudi Menuhin")) + .andExpect(xpath(composerName, NS, 1).string(equalTo("Johann Sebastian Bach"))) // Hamcrest.. + .andExpect(xpath(composerName, NS, 1).string(startsWith("Johann"))) + .andExpect(xpath(composerName, NS, 1).string(notNullValue())); + } + + @Test + public void testNumber() throws Exception { + + String composerDouble = "/ns:people/composers/composer[%s]/someDouble"; + + this.mockMvc.perform(get("/music/people")) + .andExpect(xpath(composerDouble, NS, 1).number(21d)) + .andExpect(xpath(composerDouble, NS, 2).number(.0025)) + .andExpect(xpath(composerDouble, NS, 3).number(1.6035)) + .andExpect(xpath(composerDouble, NS, 4).number(Double.NaN)) + .andExpect(xpath(composerDouble, NS, 1).number(equalTo(21d))) // Hamcrest.. + .andExpect(xpath(composerDouble, NS, 3).number(closeTo(1.6, .01))); + } + + @Test + public void testBoolean() throws Exception { + + String performerBooleanValue = "/ns:people/performers/performer[%s]/someBoolean"; + + this.mockMvc.perform(get("/music/people")) + .andExpect(xpath(performerBooleanValue, NS, 1).booleanValue(false)) + .andExpect(xpath(performerBooleanValue, NS, 2).booleanValue(true)); + } + + @Test + public void testNodeCount() throws Exception { + + this.mockMvc.perform(get("/music/people")) + .andExpect(xpath("/ns:people/composers/composer", NS).nodeCount(4)) + .andExpect(xpath("/ns:people/performers/performer", NS).nodeCount(2)) + .andExpect(xpath("/ns:people/composers/composer", NS).nodeCount(lessThan(5))) // Hamcrest.. + .andExpect(xpath("/ns:people/performers/performer", NS).nodeCount(greaterThan(0))); + } + + @Controller + private static class MusicController { + + @RequestMapping(value="/music/people") + public @ResponseBody PeopleWrapper getPeople() { + + List composers = Arrays.asList( + new Person("Johann Sebastian Bach").setSomeDouble(21), + new Person("Johannes Brahms").setSomeDouble(.0025), + new Person("Edvard Grieg").setSomeDouble(1.6035), + new Person("Robert Schumann").setSomeDouble(Double.NaN)); + + List performers = Arrays.asList( + new Person("Vladimir Ashkenazy").setSomeBoolean(false), + new Person("Yehudi Menuhin").setSomeBoolean(true)); + + return new PeopleWrapper(composers, performers); + } + } + + @SuppressWarnings("unused") + @XmlRootElement(name="people", namespace="http://example.org/music/people") + @XmlAccessorType(XmlAccessType.FIELD) + private static class PeopleWrapper { + + @XmlElementWrapper(name="composers") + @XmlElement(name="composer") + private List composers; + + @XmlElementWrapper(name="performers") + @XmlElement(name="performer") + private List performers; + + public PeopleWrapper() { + } + + public PeopleWrapper(List composers, List performers) { + this.composers = composers; + this.performers = performers; + } + + public List getComposers() { + return this.composers; + } + + public List getPerformers() { + return this.performers; + } + } + +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/setup/ConditionalDelegatingFilterProxyTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/setup/ConditionalDelegatingFilterProxyTests.java new file mode 100644 index 0000000000..f1b482b918 --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/setup/ConditionalDelegatingFilterProxyTests.java @@ -0,0 +1,272 @@ +/* + * 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.mock.servlet.setup; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockFilterConfig; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.mock.servlet.setup.PatternMappingFilterProxy; + +/** + * + * @author Rob Winch + */ +public class ConditionalDelegatingFilterProxyTests { + private MockHttpServletRequest request; + private MockHttpServletResponse response; + private MockFilterChain filterChain; + private MockFilter delegate; + private PatternMappingFilterProxy filter; + + @Before + public void setup() { + request = new MockHttpServletRequest(); + request.setContextPath("/context"); + response = new MockHttpServletResponse(); + filterChain = new MockFilterChain(); + delegate = new MockFilter(); + } + + @Test + public void init() throws Exception { + FilterConfig config = new MockFilterConfig(); + filter = new PatternMappingFilterProxy(delegate, "/"); + filter.init(config); + assertThat(delegate.filterConfig, is(config)); + } + + @Test + public void destroy() throws Exception { + filter = new PatternMappingFilterProxy(delegate, "/"); + filter.destroy(); + assertThat(delegate.destroy, is(true)); + } + + @Test + public void matchExact() throws Exception { + assertFilterInvoked("/test", "/test"); + } + + @Test + public void matchExactEmpty() throws Exception { + assertFilterInvoked("", ""); + } + + @Test + public void matchPathMappingAllFolder() throws Exception { + assertFilterInvoked("/test/this", "/*"); + } + + @Test + public void matchPathMappingAll() throws Exception { + assertFilterInvoked("/test", "/*"); + } + + @Test + public void matchPathMappingAllContextRoot() throws Exception { + assertFilterInvoked("", "/*"); + } + + @Test + public void matchPathMappingContextRootAndSlash() throws Exception { + assertFilterInvoked("/", "/*"); + } + + @Test + public void matchPathMappingFolderPatternWithMultiFolderPath() throws Exception { + assertFilterInvoked("/test/this/here", "/test/*"); + } + + @Test + public void matchPathMappingFolderPattern() throws Exception { + assertFilterInvoked("/test/this", "/test/*"); + } + + @Test + public void matchPathMappingNoSuffix() throws Exception { + assertFilterInvoked("/test/", "/test/*"); + } + + @Test + public void matchPathMappingMissingSlash() throws Exception { + assertFilterInvoked("/test", "/test/*"); + } + + @Test + public void noMatchPathMappingMulti() throws Exception { + assertFilterNotInvoked("/this/test/here", "/test/*"); + } + + @Test + public void noMatchPathMappingEnd() throws Exception { + assertFilterNotInvoked("/this/test", "/test/*"); + } + + @Test + public void noMatchPathMappingEndSuffix() throws Exception { + assertFilterNotInvoked("/test2/", "/test/*"); + } + + @Test + public void noMatchPathMappingMissingSlash() throws Exception { + assertFilterNotInvoked("/test2", "/test/*"); + } + + @Test + public void matchExtensionMulti() throws Exception { + assertFilterInvoked("/test/this/here.html", "*.html"); + } + + @Test + public void matchExtension() throws Exception { + assertFilterInvoked("/test/this.html", "*.html"); + } + + @Test + public void matchExtensionNoPrefix() throws Exception { + assertFilterInvoked("/.html", "*.html"); + } + + @Test + public void matchExtensionNoFolder() throws Exception { + assertFilterInvoked("/test.html", "*.html"); + } + + @Test + public void noMatchExtensionNoSlash() throws Exception { + assertFilterNotInvoked(".html", "*.html"); + } + + @Test + public void noMatchExtensionSlashEnd() throws Exception { + assertFilterNotInvoked("/index.html/", "*.html"); + } + + @Test + public void noMatchExtensionPeriodEnd() throws Exception { + assertFilterNotInvoked("/index.html.", "*.html"); + } + + @Test + public void noMatchExtensionLarger() throws Exception { + assertFilterNotInvoked("/index.htm", "*.html"); + } + + @Test + public void noMatchInvalidPattern() throws Exception { + // pattern uses extension mapping but starts with / (treated as exact match) + assertFilterNotInvoked("/index.html", "/*.html"); + } + + /* + * Below are tests from Table 12-1 of the Servlet Specification + */ + @Test + public void specPathMappingMultiFolderPattern() throws Exception { + assertFilterInvoked("/foo/bar/index.html", "/foo/bar/*"); + } + + @Test + public void specPathMappingMultiFolderPatternAlternate() throws Exception { + assertFilterInvoked("/foo/bar/index.bop", "/foo/bar/*"); + } + + @Test + public void specPathMappingNoSlash() throws Exception { + assertFilterInvoked("/baz", "/baz/*"); + } + + @Test + public void specPathMapping() throws Exception { + assertFilterInvoked("/baz/index.html", "/baz/*"); + } + + @Test + public void specExactMatch() throws Exception { + assertFilterInvoked("/catalog", "/catalog"); + } + + @Test + public void specExtensionMappingSingleFolder() throws Exception { + assertFilterInvoked("/catalog/racecar.bop", "*.bop"); + } + + @Test + public void specExtensionMapping() throws Exception { + assertFilterInvoked("/index.bop", "*.bop"); + } + + private void assertFilterNotInvoked(String requestUri, String pattern) throws Exception { + request.setRequestURI(request.getContextPath() + requestUri); + filter = new PatternMappingFilterProxy(delegate, pattern); + filter.doFilter(request, response, filterChain); + + assertThat(delegate.request, equalTo((ServletRequest) null)); + assertThat(delegate.response, equalTo((ServletResponse) null)); + assertThat(delegate.chain, equalTo((FilterChain) null)); + + assertThat(filterChain.getRequest(), equalTo((ServletRequest) request)); + assertThat(filterChain.getResponse(), equalTo((ServletResponse) response)); + filterChain = new MockFilterChain(); + } + + private void assertFilterInvoked(String requestUri, String pattern) throws Exception { + request.setRequestURI(request.getContextPath() + requestUri); + filter = new PatternMappingFilterProxy(delegate, pattern); + filter.doFilter(request, response, filterChain); + + assertThat(delegate.request, equalTo((ServletRequest) request)); + assertThat(delegate.response, equalTo((ServletResponse) response)); + assertThat(delegate.chain, equalTo((FilterChain) filterChain)); + delegate = new MockFilter(); + } + + private static class MockFilter implements Filter { + private FilterConfig filterConfig; + private ServletRequest request; + private ServletResponse response; + private FilterChain chain; + private boolean destroy; + + public void init(FilterConfig filterConfig) throws ServletException { + this.filterConfig = filterConfig; + } + + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, + ServletException { + this.request = request; + this.response = response; + this.chain = chain; + } + + public void destroy() { + this.destroy = true; + } + } +} diff --git a/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/setup/DefaultMockMvcBuilderTests.java b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/setup/DefaultMockMvcBuilderTests.java new file mode 100644 index 0000000000..c91d7f21cd --- /dev/null +++ b/spring-test-mvc/src/test/java/org/springframework/test/web/mock/servlet/setup/DefaultMockMvcBuilderTests.java @@ -0,0 +1,82 @@ +/* + * 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.mock.servlet.setup; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.stereotype.Controller; +import org.springframework.test.web.mock.servlet.setup.DefaultMockMvcBuilder; +import org.springframework.test.web.mock.servlet.setup.MockMvcBuilders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.filter.OncePerRequestFilter; + +/** + * Tests for {@link DefaultMockMvcBuilder}. + * + * @author Rob Winch + */ +public class DefaultMockMvcBuilderTests { + + private DefaultMockMvcBuilder builder; + + @Before + public void setup() { + builder = MockMvcBuilders.standaloneSetup(new PersonController()); + } + + @Test(expected = IllegalArgumentException.class) + public void addFiltersFiltersNull() { + builder.addFilters((Filter[]) null); + } + + @Test(expected = IllegalArgumentException.class) + public void addFiltersFiltersContainsNull() { + builder.addFilters(new ContinueFilter(), (Filter) null); + } + + @Test(expected = IllegalArgumentException.class) + public void addFilterPatternsNull() { + builder.addFilter(new ContinueFilter(), (String[]) null); + } + + @Test(expected = IllegalArgumentException.class) + public void addFilterPatternContainsNull() { + builder.addFilter(new ContinueFilter(), (String) null); + } + + + @Controller + private static class PersonController { + @RequestMapping(value="/forward") + public String forward() { + return "forward:/persons"; + } + } + + private class ContinueFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + filterChain.doFilter(request, response); + } + } + +} diff --git a/spring-test-mvc/src/test/resources/META-INF/web-resources/WEB-INF/layouts/standardLayout.jsp b/spring-test-mvc/src/test/resources/META-INF/web-resources/WEB-INF/layouts/standardLayout.jsp new file mode 100644 index 0000000000..dc3f6216ae --- /dev/null +++ b/spring-test-mvc/src/test/resources/META-INF/web-resources/WEB-INF/layouts/standardLayout.jsp @@ -0,0 +1,12 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> +<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %> + + + + +Title + + + + + \ No newline at end of file diff --git a/spring-test-mvc/src/test/resources/META-INF/web-resources/WEB-INF/layouts/tiles.xml b/spring-test-mvc/src/test/resources/META-INF/web-resources/WEB-INF/layouts/tiles.xml new file mode 100644 index 0000000000..c6e5f6240c --- /dev/null +++ b/spring-test-mvc/src/test/resources/META-INF/web-resources/WEB-INF/layouts/tiles.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/spring-test-mvc/src/test/resources/META-INF/web-resources/WEB-INF/views/home.jsp b/spring-test-mvc/src/test/resources/META-INF/web-resources/WEB-INF/views/home.jsp new file mode 100644 index 0000000000..59990b85d2 --- /dev/null +++ b/spring-test-mvc/src/test/resources/META-INF/web-resources/WEB-INF/views/home.jsp @@ -0,0 +1,2 @@ + +

Main page

diff --git a/spring-test-mvc/src/test/resources/META-INF/web-resources/WEB-INF/views/tiles.xml b/spring-test-mvc/src/test/resources/META-INF/web-resources/WEB-INF/views/tiles.xml new file mode 100644 index 0000000000..1a272df670 --- /dev/null +++ b/spring-test-mvc/src/test/resources/META-INF/web-resources/WEB-INF/views/tiles.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/spring-test-mvc/src/test/resources/META-INF/web-resources/resources/Spring.js b/spring-test-mvc/src/test/resources/META-INF/web-resources/resources/Spring.js new file mode 100644 index 0000000000..44ca644cbf --- /dev/null +++ b/spring-test-mvc/src/test/resources/META-INF/web-resources/resources/Spring.js @@ -0,0 +1,16 @@ +/* + * Copyright 2004-2008 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. + */ +Spring={};Spring.debug=true;Spring.decorations={};Spring.decorations.applied=false;Spring.initialize=function(){Spring.applyDecorations();Spring.remoting=new Spring.RemotingHandler();};Spring.addDecoration=function(_1){if(!Spring.decorations[_1.elementId]){Spring.decorations[_1.elementId]=[];Spring.decorations[_1.elementId].push(_1);}else{var _2=false;for(var i=0;i%n \ No newline at end of file diff --git a/spring-test-mvc/src/test/resources/org/springframework/test/web/mock/client/samples/ludwig.json b/spring-test-mvc/src/test/resources/org/springframework/test/web/mock/client/samples/ludwig.json new file mode 100644 index 0000000000..2b1a2f6762 --- /dev/null +++ b/spring-test-mvc/src/test/resources/org/springframework/test/web/mock/client/samples/ludwig.json @@ -0,0 +1,5 @@ +{ + "name" : "Ludwig van Beethoven", + "someDouble" : "1.6035", + "someBoolean" : "true" +} \ No newline at end of file diff --git a/spring-test-mvc/src/test/resources/org/springframework/test/web/mock/servlet/samples/context/TestContextTests-context.xml b/spring-test-mvc/src/test/resources/org/springframework/test/web/mock/servlet/samples/context/TestContextTests-context.xml new file mode 100644 index 0000000000..069aff1387 --- /dev/null +++ b/spring-test-mvc/src/test/resources/org/springframework/test/web/mock/servlet/samples/context/TestContextTests-context.xml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file diff --git a/spring-test-mvc/src/test/resources/org/springframework/test/web/mock/servlet/samples/context/security.xml b/spring-test-mvc/src/test/resources/org/springframework/test/web/mock/servlet/samples/context/security.xml new file mode 100644 index 0000000000..f6cb546bc7 --- /dev/null +++ b/spring-test-mvc/src/test/resources/org/springframework/test/web/mock/servlet/samples/context/security.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/spring-test-mvc/src/test/resources/org/springframework/test/web/mock/servlet/samples/servlet-context.xml b/spring-test-mvc/src/test/resources/org/springframework/test/web/mock/servlet/samples/servlet-context.xml new file mode 100644 index 0000000000..b592d31072 --- /dev/null +++ b/spring-test-mvc/src/test/resources/org/springframework/test/web/mock/servlet/samples/servlet-context.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + /WEB-INF/**/tiles.xml + + + + + \ No newline at end of file diff --git a/spring-test-mvc/src/test/webapp/WEB-INF/layouts/main.jsp b/spring-test-mvc/src/test/webapp/WEB-INF/layouts/main.jsp new file mode 100644 index 0000000000..408d8fc932 --- /dev/null +++ b/spring-test-mvc/src/test/webapp/WEB-INF/layouts/main.jsp @@ -0,0 +1,12 @@ +<%@ page language="java" contentType="text/html; charset=UTF-8" + pageEncoding="UTF-8"%> + + + + +Fake Layout + + + + + \ No newline at end of file diff --git a/spring-test-mvc/src/test/webapp/WEB-INF/layouts/tiles.xml b/spring-test-mvc/src/test/webapp/WEB-INF/layouts/tiles.xml new file mode 100644 index 0000000000..55c10df4fd --- /dev/null +++ b/spring-test-mvc/src/test/webapp/WEB-INF/layouts/tiles.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/spring-test-mvc/src/test/webapp/WEB-INF/readme.txt b/spring-test-mvc/src/test/webapp/WEB-INF/readme.txt new file mode 100644 index 0000000000..3e3b6e0fb1 --- /dev/null +++ b/spring-test-mvc/src/test/webapp/WEB-INF/readme.txt @@ -0,0 +1,2 @@ + +Dummy web application for testing purposes. \ No newline at end of file diff --git a/spring-test-mvc/src/test/webapp/WEB-INF/views/tiles.xml b/spring-test-mvc/src/test/webapp/WEB-INF/views/tiles.xml new file mode 100644 index 0000000000..481d7625fc --- /dev/null +++ b/spring-test-mvc/src/test/webapp/WEB-INF/views/tiles.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/spring-test-mvc/src/test/webapp/resources/Spring.js b/spring-test-mvc/src/test/webapp/resources/Spring.js new file mode 100644 index 0000000000..44ca644cbf --- /dev/null +++ b/spring-test-mvc/src/test/webapp/resources/Spring.js @@ -0,0 +1,16 @@ +/* + * Copyright 2004-2008 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. + */ +Spring={};Spring.debug=true;Spring.decorations={};Spring.decorations.applied=false;Spring.initialize=function(){Spring.applyDecorations();Spring.remoting=new Spring.RemotingHandler();};Spring.addDecoration=function(_1){if(!Spring.decorations[_1.elementId]){Spring.decorations[_1.elementId]=[];Spring.decorations[_1.elementId].push(_1);}else{var _2=false;for(var i=0;i