Add AssertJ support for MockMvc
This commit adds a new way to use MockMvc by returning a MvcResult that is AssertJ compatible. Compared to the existing MockMvc infrastructure, this new model has the following advantages: * Can be created from a MockMvc instance * Dedicated builder methods for easier setup * Assertions use familiar AssertJ syntax with discoverable assertions through a DSL * Improved exception message See gh-21178 Co-authored-by: Brian Clozel <brian.clozel@broadcom.com>
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.web.servlet.assertj;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.RequestBuilder;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
/**
|
||||
* {@link MockMvc} variant that tests Spring MVC exchanges and provide fluent
|
||||
* assertions using {@link org.assertj.core.api.Assertions AssertJ}.
|
||||
*
|
||||
* <p>A main difference with {@link MockMvc} is that an unresolved exception
|
||||
* is not thrown directly. Rather an {@link AssertableMvcResult} is available
|
||||
* with an {@link AssertableMvcResult#getUnresolvedException() unresolved
|
||||
* exception}.
|
||||
*
|
||||
* <p>{@link AssertableMockMvc} can be configured with a list of
|
||||
* {@linkplain HttpMessageConverter HttpMessageConverters} to allow response
|
||||
* body to be deserialized, rather than asserting on the raw values.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Brian Clozel
|
||||
* @since 6.2
|
||||
*/
|
||||
public final class AssertableMockMvc {
|
||||
|
||||
private static final MediaType JSON = MediaType.APPLICATION_JSON;
|
||||
|
||||
private final MockMvc mockMvc;
|
||||
|
||||
@Nullable
|
||||
private final GenericHttpMessageConverter<Object> jsonMessageConverter;
|
||||
|
||||
|
||||
private AssertableMockMvc(MockMvc mockMvc, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) {
|
||||
Assert.notNull(mockMvc, "mockMVC should not be null");
|
||||
this.mockMvc = mockMvc;
|
||||
this.jsonMessageConverter = jsonMessageConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link AssertableMockMvc} instance that delegates to the
|
||||
* given {@link MockMvc}.
|
||||
* @param mockMvc the MockMvc instance to delegate calls to
|
||||
*/
|
||||
public static AssertableMockMvc create(MockMvc mockMvc) {
|
||||
return new AssertableMockMvc(mockMvc, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link AssertableMockMvc} instance using the given, fully
|
||||
* initialized (i.e., <em>refreshed</em>) {@link WebApplicationContext}. The
|
||||
* given {@code customizations} are applied to the {@link DefaultMockMvcBuilder}
|
||||
* that ultimately creates the underlying {@link MockMvc} instance.
|
||||
* <p>If no further customization of the underlying {@link MockMvc} instance
|
||||
* is required, use {@link #from(WebApplicationContext)}.
|
||||
* @param applicationContext the application context to detect the Spring
|
||||
* MVC infrastructure and application controllers from
|
||||
* @param customizations the function that creates a {@link MockMvc}
|
||||
* instance based on a {@link DefaultMockMvcBuilder}.
|
||||
* @see MockMvcBuilders#webAppContextSetup(WebApplicationContext)
|
||||
*/
|
||||
public static AssertableMockMvc from(WebApplicationContext applicationContext,
|
||||
Function<DefaultMockMvcBuilder, MockMvc> customizations) {
|
||||
|
||||
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(applicationContext);
|
||||
MockMvc mockMvc = customizations.apply(builder);
|
||||
return create(mockMvc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to create a {@link AssertableMockMvc} instance using the given,
|
||||
* fully initialized (i.e., <em>refreshed</em>) {@link WebApplicationContext}.
|
||||
* <p>Consider using {@link #from(WebApplicationContext, Function)} if
|
||||
* further customizations of the underlying {@link MockMvc} instance is
|
||||
* required.
|
||||
* @param applicationContext the application context to detect the Spring
|
||||
* MVC infrastructure and application controllers from
|
||||
* @see MockMvcBuilders#webAppContextSetup(WebApplicationContext)
|
||||
*/
|
||||
public static AssertableMockMvc from(WebApplicationContext applicationContext) {
|
||||
return from(applicationContext, DefaultMockMvcBuilder::build);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link AssertableMockMvc} instance by registering one or more
|
||||
* {@code @Controller} instances and configuring Spring MVC infrastructure
|
||||
* programmatically.
|
||||
* <p>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.
|
||||
* @param controllers one or more {@code @Controller} instances to test
|
||||
* (specified {@code Class} will be turned into instance)
|
||||
* @param customizations the function that creates a {@link MockMvc}
|
||||
* instance based on a {@link StandaloneMockMvcBuilder}, typically to
|
||||
* configure the Spring MVC infrastructure
|
||||
* @see MockMvcBuilders#standaloneSetup(Object...)
|
||||
*/
|
||||
public static AssertableMockMvc of(Collection<?> controllers,
|
||||
Function<StandaloneMockMvcBuilder, MockMvc> customizations) {
|
||||
|
||||
StandaloneMockMvcBuilder builder = MockMvcBuilders.standaloneSetup(controllers.toArray());
|
||||
return create(customizations.apply(builder));
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut to create a {@link AssertableMockMvc} instance by registering
|
||||
* one or more {@code @Controller} instances.
|
||||
* <p>The minimum infrastructure required by the
|
||||
* {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}
|
||||
* to serve requests with annotated controllers is created. Consider using
|
||||
* {@link #of(Collection, Function)} if additional configuration of the MVC
|
||||
* infrastructure is required.
|
||||
* @param controllers one or more {@code @Controller} instances to test
|
||||
* (specified {@code Class} will be turned into instance)
|
||||
* @see MockMvcBuilders#standaloneSetup(Object...)
|
||||
*/
|
||||
public static AssertableMockMvc of(Object... controllers) {
|
||||
return of(Arrays.asList(controllers), StandaloneMockMvcBuilder::build);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@link AssertableMockMvc} instance using the specified
|
||||
* {@link HttpMessageConverter}. If none are specified, only basic assertions
|
||||
* on the response body can be performed. Consider registering a suitable
|
||||
* JSON converter for asserting data structure.
|
||||
* @param httpMessageConverters the message converters to use
|
||||
* @return a new instance using the specified converters
|
||||
*/
|
||||
public AssertableMockMvc withHttpMessageConverters(Iterable<HttpMessageConverter<?>> httpMessageConverters) {
|
||||
return new AssertableMockMvc(this.mockMvc, findJsonMessageConverter(httpMessageConverters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a request and return a type that can be used with standard
|
||||
* {@link org.assertj.core.api.Assertions AssertJ} assertions.
|
||||
* <p>Use static methods of {@link MockMvcRequestBuilders} to prepare the
|
||||
* request, wrapping the invocation in {@code assertThat}. The following
|
||||
* asserts that a {@linkplain MockMvcRequestBuilders#get(URI) GET} request
|
||||
* against "/greet" has an HTTP status code 200 (OK), and a simple body:
|
||||
* <pre><code class='java'>assertThat(mvc.perform(get("/greet")))
|
||||
* .hasStatusOk()
|
||||
* .body().asString().isEqualTo("Hello");
|
||||
* </code></pre>
|
||||
* <p>Contrary to {@link MockMvc#perform(RequestBuilder)}, this does not
|
||||
* throw an exception if the request fails with an unresolved exception.
|
||||
* Rather, the result provides the exception, if any. Assuming that a
|
||||
* {@linkplain MockMvcRequestBuilders#post(URI) POST} request against
|
||||
* {@code /boom} throws an {@code IllegalStateException}, the following
|
||||
* asserts that the invocation has indeed failed with the expected error
|
||||
* message:
|
||||
* <pre><code class='java'>assertThat(mvc.perform(post("/boom")))
|
||||
* .unresolvedException().isInstanceOf(IllegalStateException.class)
|
||||
* .hasMessage("Expected");
|
||||
* </code></pre>
|
||||
* <p>
|
||||
* @param requestBuilder used to prepare the request to execute;
|
||||
* see static factory methods in
|
||||
* {@link org.springframework.test.web.servlet.request.MockMvcRequestBuilders}
|
||||
* @return an {@link AssertableMvcResult} to be wrapped in {@code assertThat}
|
||||
* @see MockMvc#perform(RequestBuilder)
|
||||
*/
|
||||
public AssertableMvcResult perform(RequestBuilder requestBuilder) {
|
||||
Object result = getMvcResultOrFailure(requestBuilder);
|
||||
if (result instanceof MvcResult mvcResult) {
|
||||
return new DefaultAssertableMvcResult(mvcResult, null, this.jsonMessageConverter);
|
||||
}
|
||||
else {
|
||||
return new DefaultAssertableMvcResult(null, (Exception) result, this.jsonMessageConverter);
|
||||
}
|
||||
}
|
||||
|
||||
private Object getMvcResultOrFailure(RequestBuilder requestBuilder) {
|
||||
try {
|
||||
return this.mockMvc.perform(requestBuilder).andReturn();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return ex;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
private GenericHttpMessageConverter<Object> findJsonMessageConverter(
|
||||
Iterable<HttpMessageConverter<?>> messageConverters) {
|
||||
|
||||
return StreamSupport.stream(messageConverters.spliterator(), false)
|
||||
.filter(GenericHttpMessageConverter.class::isInstance)
|
||||
.map(GenericHttpMessageConverter.class::cast)
|
||||
.filter(converter -> converter.canWrite(null, Map.class, JSON))
|
||||
.filter(converter -> converter.canRead(Map.class, JSON))
|
||||
.findFirst().orElse(null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.web.servlet.assertj;
|
||||
|
||||
import org.assertj.core.api.AssertProvider;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
|
||||
/**
|
||||
* A {@link MvcResult} that additionally supports AssertJ style assertions.
|
||||
*
|
||||
* <p>Can be in two distinct states:
|
||||
* <ol>
|
||||
* <li>The request processed successfully, and {@link #getUnresolvedException()}
|
||||
* is therefore {@code null}.</li>
|
||||
* <li>The request failed unexpectedly with {@link #getUnresolvedException()}
|
||||
* providing more information about the error. Any attempt to access a
|
||||
* member of the result fails with an exception.</li>
|
||||
* </ol>
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Brian Clozel
|
||||
* @since 6.2
|
||||
* @see AssertableMockMvc
|
||||
*/
|
||||
public interface AssertableMvcResult extends MvcResult, AssertProvider<MvcResultAssert> {
|
||||
|
||||
/**
|
||||
* Return the exception that was thrown unexpectedly while processing the
|
||||
* request, if any.
|
||||
*/
|
||||
@Nullable
|
||||
Exception getUnresolvedException();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.web.servlet.assertj;
|
||||
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.web.servlet.FlashMap;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* The default {@link AssertableMvcResult} implementation.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.2
|
||||
*/
|
||||
final class DefaultAssertableMvcResult implements AssertableMvcResult {
|
||||
|
||||
@Nullable
|
||||
private final MvcResult target;
|
||||
|
||||
@Nullable
|
||||
private final Exception unresolvedException;
|
||||
|
||||
@Nullable
|
||||
private final GenericHttpMessageConverter<Object> jsonMessageConverter;
|
||||
|
||||
DefaultAssertableMvcResult(@Nullable MvcResult target, @Nullable Exception unresolvedException, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) {
|
||||
this.target = target;
|
||||
this.unresolvedException = unresolvedException;
|
||||
this.jsonMessageConverter = jsonMessageConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the exception that was thrown unexpectedly while processing the
|
||||
* request, if any.
|
||||
*/
|
||||
@Nullable
|
||||
public Exception getUnresolvedException() {
|
||||
return this.unresolvedException;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletRequest getRequest() {
|
||||
return getTarget().getRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockHttpServletResponse getResponse() {
|
||||
return getTarget().getResponse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getHandler() {
|
||||
return getTarget().getHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerInterceptor[] getInterceptors() {
|
||||
return getTarget().getInterceptors();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModelAndView getModelAndView() {
|
||||
return getTarget().getModelAndView();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Exception getResolvedException() {
|
||||
return getTarget().getResolvedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlashMap getFlashMap() {
|
||||
return getTarget().getFlashMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAsyncResult() {
|
||||
return getTarget().getAsyncResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAsyncResult(long timeToWait) {
|
||||
return getTarget().getAsyncResult(timeToWait);
|
||||
}
|
||||
|
||||
|
||||
private MvcResult getTarget() {
|
||||
if (this.target == null) {
|
||||
throw new IllegalStateException(
|
||||
"Request has failed with unresolved exception " + this.unresolvedException);
|
||||
}
|
||||
return this.target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use AssertJ's {@link org.assertj.core.api.Assertions#assertThat assertThat}
|
||||
* instead.
|
||||
*/
|
||||
@Override
|
||||
public MvcResultAssert assertThat() {
|
||||
return new MvcResultAssert(this, this.jsonMessageConverter);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright 2002-2024 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.test.web.servlet.assertj;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import jakarta.servlet.http.Cookie;
|
||||
import org.assertj.core.api.AbstractStringAssert;
|
||||
import org.assertj.core.api.AbstractThrowableAssert;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.api.MapAssert;
|
||||
import org.assertj.core.api.ObjectAssert;
|
||||
import org.assertj.core.error.BasicErrorMessageFactory;
|
||||
import org.assertj.core.internal.Failures;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.test.http.MediaTypeAssert;
|
||||
import org.springframework.test.web.servlet.MvcResult;
|
||||
import org.springframework.test.web.servlet.ResultHandler;
|
||||
import org.springframework.test.web.servlet.ResultMatcher;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
|
||||
/**
|
||||
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied
|
||||
* to {@link MvcResult}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @author Brian Clozel
|
||||
* @since 6.2
|
||||
*/
|
||||
public class MvcResultAssert extends AbstractMockHttpServletResponseAssert<MvcResultAssert, AssertableMvcResult> {
|
||||
|
||||
MvcResultAssert(AssertableMvcResult mvcResult, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) {
|
||||
super(jsonMessageConverter, mvcResult, MvcResultAssert.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MockHttpServletResponse getResponse() {
|
||||
checkHasNotFailedUnexpectedly();
|
||||
return this.actual.getResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the request has failed with an unresolved exception, and
|
||||
* return a new {@linkplain AbstractThrowableAssert assertion} object
|
||||
* that uses the unresolved {@link Exception} as the object to test.
|
||||
*/
|
||||
public AbstractThrowableAssert<?, ? extends Throwable> unresolvedException() {
|
||||
hasUnresolvedException();
|
||||
return Assertions.assertThat(this.actual.getUnresolvedException());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@linkplain AbstractMockHttpServletRequestAssert assertion}
|
||||
* object that uses the {@link MockHttpServletRequest} as the object to test.
|
||||
*/
|
||||
public AbstractMockHttpServletRequestAssert<?> request() {
|
||||
checkHasNotFailedUnexpectedly();
|
||||
return new MockHttpRequestAssert(this.actual.getRequest());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@linkplain CookieMapAssert assertion} object that uses the
|
||||
* response's {@linkplain Cookie cookies} as the object to test.
|
||||
*/
|
||||
public CookieMapAssert cookies() {
|
||||
checkHasNotFailedUnexpectedly();
|
||||
return new CookieMapAssert(this.actual.getResponse().getCookies());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@linkplain MediaTypeAssert assertion} object that uses the
|
||||
* response's {@linkplain MediaType content type} as the object to test.
|
||||
*/
|
||||
public MediaTypeAssert contentType() {
|
||||
checkHasNotFailedUnexpectedly();
|
||||
return new MediaTypeAssert(this.actual.getResponse().getContentType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@linkplain HandlerResultAssert assertion} object that uses
|
||||
* the handler as the object to test. For a method invocation on a
|
||||
* controller, this is relative method handler
|
||||
* Example: <pre><code class='java'>
|
||||
* // Check that a GET to "/greet" is invoked on a "handleGreet" method name
|
||||
* assertThat(mvc.perform(get("/greet")).handler().method().hasName("sayGreet");
|
||||
* </code></pre>
|
||||
*/
|
||||
public HandlerResultAssert handler() {
|
||||
checkHasNotFailedUnexpectedly();
|
||||
return new HandlerResultAssert(this.actual.getHandler());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a {@link ModelAndView} is available and return a new
|
||||
* {@linkplain ModelAssert assertion} object that uses the
|
||||
* {@linkplain ModelAndView#getModel() model} as the object to test.
|
||||
*/
|
||||
public ModelAssert model() {
|
||||
checkHasNotFailedUnexpectedly();
|
||||
return new ModelAssert(getModelAndView().getModel());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a {@link ModelAndView} is available and return a new
|
||||
* {@linkplain AbstractStringAssert assertion} object that uses the
|
||||
* {@linkplain ModelAndView#getViewName()} view name} as the object to test.
|
||||
* @see #hasViewName(String)
|
||||
*/
|
||||
public AbstractStringAssert<?> viewName() {
|
||||
checkHasNotFailedUnexpectedly();
|
||||
return Assertions.assertThat(getModelAndView().getViewName()).as("View name");
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@linkplain MapAssert assertion} object that uses the
|
||||
* "output" flash attributes saved during request processing as the object
|
||||
* to test.
|
||||
*/
|
||||
public MapAssert<String, Object> flash() {
|
||||
checkHasNotFailedUnexpectedly();
|
||||
return new MapAssert<>(this.actual.getFlashMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that an {@linkplain AbstractHttpServletRequestAssert#hasAsyncStarted(boolean)
|
||||
* asynchronous processing has started} and return a new
|
||||
* {@linkplain ObjectAssert assertion} object that uses the asynchronous
|
||||
* result as the object to test.
|
||||
*/
|
||||
public ObjectAssert<Object> asyncResult() {
|
||||
request().hasAsyncStarted(true);
|
||||
return Assertions.assertThat(this.actual.getAsyncResult()).as("Async result");
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the request has failed with an unresolved exception.
|
||||
* @see #unresolvedException()
|
||||
*/
|
||||
public MvcResultAssert hasUnresolvedException() {
|
||||
Assertions.assertThat(this.actual.getUnresolvedException())
|
||||
.withFailMessage("Expecting request to have failed but it has succeeded").isNotNull();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the request has not failed with an unresolved exception.
|
||||
*/
|
||||
public MvcResultAssert doesNotHaveUnresolvedException() {
|
||||
Assertions.assertThat(this.actual.getUnresolvedException())
|
||||
.withFailMessage("Expecting request to have succeeded but it has failed").isNull();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual mvc result matches the given {@link ResultMatcher}.
|
||||
* @param resultMatcher the result matcher to invoke
|
||||
*/
|
||||
public MvcResultAssert matches(ResultMatcher resultMatcher) {
|
||||
checkHasNotFailedUnexpectedly();
|
||||
return super.satisfies(resultMatcher::match);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the given {@link ResultHandler} to the actual mvc result.
|
||||
* @param resultHandler the result matcher to invoke
|
||||
*/
|
||||
public MvcResultAssert apply(ResultHandler resultHandler) {
|
||||
checkHasNotFailedUnexpectedly();
|
||||
return satisfies(resultHandler::handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that a {@link ModelAndView} is available with a view equals to
|
||||
* the given one. For more advanced assertions, consider using
|
||||
* {@link #viewName()}
|
||||
* @param viewName the expected view name
|
||||
*/
|
||||
public MvcResultAssert hasViewName(String viewName) {
|
||||
viewName().isEqualTo(viewName);
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
|
||||
private ModelAndView getModelAndView() {
|
||||
ModelAndView modelAndView = this.actual.getModelAndView();
|
||||
Assertions.assertThat(modelAndView).as("ModelAndView").isNotNull();
|
||||
return modelAndView;
|
||||
}
|
||||
|
||||
protected void checkHasNotFailedUnexpectedly() {
|
||||
Exception unresolvedException = this.actual.getUnresolvedException();
|
||||
if (unresolvedException != null) {
|
||||
throw Failures.instance().failure(this.info,
|
||||
new RequestFailedUnexpectedly(unresolvedException));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MockHttpRequestAssert extends AbstractMockHttpServletRequestAssert<MockHttpRequestAssert> {
|
||||
|
||||
private MockHttpRequestAssert(MockHttpServletRequest request) {
|
||||
super(request, MockHttpRequestAssert.class);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RequestFailedUnexpectedly extends BasicErrorMessageFactory {
|
||||
|
||||
private RequestFailedUnexpectedly(Exception ex) {
|
||||
super("%nRequest has failed unexpectedly:%n%s", unquotedString(getIndentedStackTraceAsString(ex)));
|
||||
}
|
||||
|
||||
private static String getIndentedStackTraceAsString(Throwable ex) {
|
||||
String stackTrace = getStackTraceAsString(ex);
|
||||
return indent(stackTrace);
|
||||
}
|
||||
|
||||
private static String getStackTraceAsString(Throwable ex) {
|
||||
StringWriter writer = new StringWriter();
|
||||
PrintWriter printer = new PrintWriter(writer);
|
||||
ex.printStackTrace(printer);
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
private static String indent(String input) {
|
||||
BufferedReader reader = new BufferedReader(new StringReader(input));
|
||||
StringWriter writer = new StringWriter();
|
||||
PrintWriter printer = new PrintWriter(writer);
|
||||
reader.lines().forEach(line -> {
|
||||
printer.print(" ");
|
||||
printer.println(line);
|
||||
});
|
||||
return writer.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user