Overview
As discussed in https://github.com/spring-projects/spring-framework/issues/21178#issuecomment-512774918, it would be possible to provide generic hooks in MockMvc
APIs that allow one to use any assertion library of choice. Such generic hooks could even be used for purposes other than performing assertions.
Due to the usefulness of such general purpose hooks, we should consider introducing them in various MockMvc
APIs even if we do not provide specific support for a fluent assertion API such as AssertJ (see gh-21178).
The introduction of such hooks would also align with similar support provided in WebTestClient
-- for example, WebTestClient.BodySpec.value(Consumer<B>)
.
Deliverables
TBD
Comment From: simonbasle
This could be implemented as a satisfies(Consumer<B>)
method on various xxxResultMatchers
classes of the MockMvc hierarchy. The naming is borrowed from AssertJ's own similar consumer-based assertion.
for instance, to assert/consume status codes, we'd add this to StatusResultMatchers
:
public ResultMatcher satisfies(IntConsumer satisfyConsumer) {
return result -> satisfyConsumer.accept(result.getResponse().getStatus());
}
usage example with AssertJ:
this.mockMvc.perform(post("/persons"))
.andExpect(status().satisfies(code -> Assertions.assertThat(code).isBetween(100, 300)));
The relevant resultmatchers seems to be most classes in org.springframework.test.web.servlet.result
:
- ContentResultMatchers
- CookieResultMatchers
- FlashAttributeResultMatchers
- HandlerResultMatchers
- HeaderResultMatchers
- JsonPathResultMatchers
- ModelResultMatchers
- RequestResultMatchers
- StatusResultMatchers
- ViewResultMatchers
- XpathResultMatchers
Comment From: simonbasle
some of these seem to have multiple "target" that they internally use. eg. StatusResultMatchers
targets primarily result.getResponse().getStatus()
but also has a few methods targeting result.getResponse().getErrorMessage()
... 🤔
Comment From: sbrannen
some of these seem to have multiple "target" that they internally use. eg.
StatusResultMatchers
targets primarilyresult.getResponse().getStatus()
but also has a few methods targetingresult.getResponse().getErrorMessage()
... 🤔
Indeed. The single satisfies(Consumer<B>)
method approach seems quite elegant until you run into collisions for the same type (e.g., String
for character encoding vs. expected content in ContentResultMatchers
).
Thanks for brainstorming though! 👍
It's good to toss around a few ideas.
Comment From: sbrannen
Though, perhaps it's not so bad to introduce sibling consumer methods like:
StatusResultMatchers#codeSatisfies(IntConsumer action)
status().codeSatisfies(code -> assertThat(code).isEqualTo(200))
- or perhaps better:
status().code(code -> assertThat(code).isEqualTo(200))
StatusResultMatchers#reasonSatisfies(Consumer<String> action)
status().reasonSatisfies(reason -> assertThat(reason).isEqualTo("Expired token"))
- or perhaps better:
status().reason(reason -> assertThat(reason).isEqualTo("Expired token"))
or similarly:
ContentResultMatchers#encoding(Consumer<String> action)
content().encoding(encoding -> assertThat(encoding).isEqualTo("UTF-8"))
ContentResultMatchers#string(Consumer<String> action)
content().string(body -> assertThat(body).contains("hello"))
@simonbasle & @rstoyanchev, thoughts?
Comment From: nightswimmings
I like more the Satisfies approach, even though is much more verbose, it is clear in its intention (just a personal opinion)
Comment From: sbrannen
I like more the Satisfies approach, even though is much more verbose, it is clear in its intention
Do you mean the variants like the following?
status().codeSatisfies(code -> assertThat(code).isEqualTo(200))
Comment From: sbrannen
@bclozel and @snicoll, would you consider this superseded by your work on #21178?
Comment From: bclozel
To me this looks like a duplicate of #21178 in the first place, so yes let's close it.