Skip to content

Commit

Permalink
Provide better name for entry points
Browse files Browse the repository at this point in the history
This commit renames AssertableMockMvc to MockMvcTester as the former
was too mouthful, and we are looking for a naming pattern that can be
applied consistently to test utilities that rely on AssertJ.

Rather than AssertableMvcResult, we now use MvcTestResult and it no
longer extends from MvcResult itself. That avoids having existing code
that is migrated to the new API whilst assigning the result to MvcResult
and get a bad DevXP as that doesn't bring any of the AssertJ support.

See gh-32712
  • Loading branch information
snicoll committed May 7, 2024
1 parent 5567d14 commit c8967de
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 192 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,107 +21,64 @@
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.
* The default {@link MvcTestResult} implementation.
*
* @author Stephane Nicoll
* @since 6.2
*/
final class DefaultAssertableMvcResult implements AssertableMvcResult {
final class DefaultMvcTestResult implements MvcTestResult {

@Nullable
private final MvcResult target;
private final MvcResult mvcResult;

@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;
DefaultMvcTestResult(@Nullable MvcResult mvcResult, @Nullable Exception unresolvedException, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) {
this.mvcResult = mvcResult;
this.unresolvedException = unresolvedException;
this.jsonMessageConverter = jsonMessageConverter;
}

/**
* Return the exception that was thrown unexpectedly while processing the
* request, if any.
*/
public MvcResult getMvcResult() {
if (this.mvcResult == null) {
throw new IllegalStateException(
"Request has failed with unresolved exception " + this.unresolvedException);
}
return this.mvcResult;
}

@Nullable
public Exception getUnresolvedException() {
return this.unresolvedException;
}

@Override
public MockHttpServletRequest getRequest() {
return getTarget().getRequest();
return getMvcResult().getRequest();
}

@Override
public MockHttpServletResponse getResponse() {
return getTarget().getResponse();
}

@Override
@Nullable
public Object getHandler() {
return getTarget().getHandler();
}

@Override
@Nullable
public HandlerInterceptor[] getInterceptors() {
return getTarget().getInterceptors();
}

@Override
@Nullable
public ModelAndView getModelAndView() {
return getTarget().getModelAndView();
return getMvcResult().getResponse();
}

@Override
@Nullable
public Exception getResolvedException() {
return getTarget().getResolvedException();
}

@Override
public FlashMap getFlashMap() {
return getTarget().getFlashMap();
}

@Override
public Object getAsyncResult() {
return getTarget().getAsyncResult();
return getMvcResult().getResolvedException();
}

@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);
public MvcTestResultAssert assertThat() {
return new MvcTestResultAssert(this, this.jsonMessageConverter);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,54 @@
import org.springframework.web.context.WebApplicationContext;

/**
* {@link MockMvc} variant that tests Spring MVC exchanges and provides fluent
* assertions using {@link org.assertj.core.api.Assertions AssertJ}.
* Test Spring MVC applications with {@link MockMvc} for server request handling
* using {@link org.assertj.core.api.Assertions AssertJ}.
*
* <p>A tester instance can be created from a {@link WebApplicationContext}:
* <pre><code class='java'>
* // Create an instance with default settings
* MockMvcTester mvc = MockMvcTester.from(applicationContext);
*
* // Create an instance with a custom Filter
* MockMvcTester mvc = MockMvcTester.from(applicationContext,
* builder -> builder.addFilters(filter).build());
* </code></pre>
*
* <p>A tester can be created standalone by providing the controller(s) to
* include in a standalone setup:<pre><code class='java'>
* // Create an instance for PersonController
* MockMvcTester mvc = MockMvcTester.of(new PersonController());
* </code></pre>
*
* <p>Once a test instance is available, you can perform requests in
* a similar fashion as with {@link MockMvc}, and wrapping the result in
* {@code assertThat} provides access to assertions. For instance:
* <pre><code class='java'>
* // perform a GET on /hi and assert the response body is equal to Hello
* assertThat(mvc.perform(get("/hi")))
* .hasStatusOk().hasBodyTextEqualTo("Hello");
* </code></pre>
*
* <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}.
* is not thrown directly. Rather an {@link MvcTestResult} is available
* with an {@link MvcTestResult#getUnresolvedException() unresolved
* exception}. You can assert that a request has failed unexpectedly:
* <pre><code class='java'>
* // perform a GET on /hi and assert the response body is equal to Hello
* assertThat(mvc.perform(get("/boom")))
* .hasUnresolvedException())
* .withMessage("Test exception");
* </code></pre>
*
* <p>{@link AssertableMockMvc} can be configured with a list of
* <p>{@link MockMvcTester} can be configured with a list of
* {@linkplain HttpMessageConverter message converters} to allow the 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 {
public final class MockMvcTester {

private static final MediaType JSON = MediaType.APPLICATION_JSON;

Expand All @@ -64,23 +95,23 @@ public final class AssertableMockMvc {
private final GenericHttpMessageConverter<Object> jsonMessageConverter;


private AssertableMockMvc(MockMvc mockMvc, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) {
private MockMvcTester(MockMvc mockMvc, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) {
Assert.notNull(mockMvc, "mockMVC should not be null");
this.mockMvc = mockMvc;
this.jsonMessageConverter = jsonMessageConverter;
}

/**
* Create a {@link AssertableMockMvc} instance that delegates to the given
* Create a {@link MockMvcTester} instance that delegates to the given
* {@link MockMvc} instance.
* @param mockMvc the MockMvc instance to delegate calls to
*/
public static AssertableMockMvc create(MockMvc mockMvc) {
return new AssertableMockMvc(mockMvc, null);
public static MockMvcTester create(MockMvc mockMvc) {
return new MockMvcTester(mockMvc, null);
}

/**
* Create an {@link AssertableMockMvc} instance using the given, fully
* Create an {@link MockMvcTester} 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.
Expand All @@ -92,7 +123,7 @@ public static AssertableMockMvc create(MockMvc mockMvc) {
* instance based on a {@link DefaultMockMvcBuilder}.
* @see MockMvcBuilders#webAppContextSetup(WebApplicationContext)
*/
public static AssertableMockMvc from(WebApplicationContext applicationContext,
public static MockMvcTester from(WebApplicationContext applicationContext,
Function<DefaultMockMvcBuilder, MockMvc> customizations) {

DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(applicationContext);
Expand All @@ -101,7 +132,7 @@ public static AssertableMockMvc from(WebApplicationContext applicationContext,
}

/**
* Shortcut to create an {@link AssertableMockMvc} instance using the given,
* Shortcut to create an {@link MockMvcTester} instance using the given,
* fully initialized (i.e., <em>refreshed</em>) {@link WebApplicationContext}.
* <p>Consider using {@link #from(WebApplicationContext, Function)} if
* further customization of the underlying {@link MockMvc} instance is
Expand All @@ -110,12 +141,12 @@ public static AssertableMockMvc from(WebApplicationContext applicationContext,
* MVC infrastructure and application controllers from
* @see MockMvcBuilders#webAppContextSetup(WebApplicationContext)
*/
public static AssertableMockMvc from(WebApplicationContext applicationContext) {
public static MockMvcTester from(WebApplicationContext applicationContext) {
return from(applicationContext, DefaultMockMvcBuilder::build);
}

/**
* Create an {@link AssertableMockMvc} instance by registering one or more
* Create an {@link MockMvcTester} 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
Expand All @@ -129,16 +160,16 @@ public static AssertableMockMvc from(WebApplicationContext applicationContext) {
* Spring MVC infrastructure
* @see MockMvcBuilders#standaloneSetup(Object...)
*/
public static AssertableMockMvc of(Collection<?> controllers,
public static MockMvcTester of(Collection<?> controllers,
Function<StandaloneMockMvcBuilder, MockMvc> customizations) {

StandaloneMockMvcBuilder builder = MockMvcBuilders.standaloneSetup(controllers.toArray());
return create(customizations.apply(builder));
}

/**
* Shortcut to create an {@link AssertableMockMvc} instance by registering
* one or more {@code @Controller} instances.
* Shortcut to create an {@link MockMvcTester} 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
Expand All @@ -149,26 +180,26 @@ public static AssertableMockMvc of(Collection<?> controllers,
* into an instance
* @see MockMvcBuilders#standaloneSetup(Object...)
*/
public static AssertableMockMvc of(Object... controllers) {
public static MockMvcTester of(Object... controllers) {
return of(Arrays.asList(controllers), StandaloneMockMvcBuilder::build);
}

/**
* Return a new {@link AssertableMockMvc} instance using the specified
* Return a new {@link MockMvcTester} instance using the specified
* {@linkplain HttpMessageConverter message converters}.
* <p>If none are specified, only basic assertions on the response body can
* be performed. Consider registering a suitable JSON converter for asserting
* against JSON data structures.
* @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));
public MockMvcTester withHttpMessageConverters(Iterable<HttpMessageConverter<?>> httpMessageConverters) {
return new MockMvcTester(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.
* Perform a request and return a {@link MvcTestResult result} 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
Expand All @@ -191,16 +222,16 @@ public AssertableMockMvc withHttpMessageConverters(Iterable<HttpMessageConverter
* @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}
* @return an {@link MvcTestResult} to be wrapped in {@code assertThat}
* @see MockMvc#perform(RequestBuilder)
*/
public AssertableMvcResult perform(RequestBuilder requestBuilder) {
public MvcTestResult perform(RequestBuilder requestBuilder) {
Object result = getMvcResultOrFailure(requestBuilder);
if (result instanceof MvcResult mvcResult) {
return new DefaultAssertableMvcResult(mvcResult, null, this.jsonMessageConverter);
return new DefaultMvcTestResult(mvcResult, null, this.jsonMessageConverter);
}
else {
return new DefaultAssertableMvcResult(null, (Exception) result, this.jsonMessageConverter);
return new DefaultMvcTestResult(null, (Exception) result, this.jsonMessageConverter);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,34 @@
import org.springframework.test.web.servlet.MvcResult;

/**
* A {@link MvcResult} that additionally supports AssertJ style assertions.
* Provide to the result of an executed request using {@link MockMvcTester} that
* is meant to be used with {@link org.assertj.core.api.Assertions#assertThat(AssertProvider)
* assertThat}.
*
* <p>Can be in one of two distinct states:
* <ol>
* <li>The request processed successfully, and {@link #getUnresolvedException()}
* is therefore {@code null}.</li>
* <li>The request processed successfully, even if it fails with an exception
* that has been resolved. {@link #getMvcResult()} is available and
* {@link #getUnresolvedException()} is {@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>
* providing more information about the error. Any attempt to access
* {@link #getMvcResult() the result } fails with an exception.</li>
* </ol>
*
* @author Stephane Nicoll
* @author Brian Clozel
* @since 6.2
* @see AssertableMockMvc
* @see MockMvcTester
*/
public interface AssertableMvcResult extends MvcResult, AssertProvider<MvcResultAssert> {
public interface MvcTestResult extends AssertProvider<MvcTestResultAssert> {

/**
* Return the {@link MvcResult result} of the processing.
* <p>If the request has failed unexpectedly, this throws an
* {@link IllegalStateException}.
* @return the {@link MvcResult}
*/
MvcResult getMvcResult();

/**
* Return the exception that was thrown unexpectedly while processing the
Expand Down

0 comments on commit c8967de

Please sign in to comment.