-
Notifications
You must be signed in to change notification settings - Fork 38.6k
Add RestTestClient #34428
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add RestTestClient #34428
Conversation
3d73111
to
99c28f0
Compare
99c28f0
to
8cb44e8
Compare
8cb44e8
to
d049e0d
Compare
d049e0d
to
4196fc3
Compare
4196fc3
to
99afe38
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the detailed contribution.
Generally, seems okay on a first pass. Some initial comments on packaging and naming. Let me know if you would you like to continue to iterate over the PR based on my feedback, or if you would prefer me to take over?
I see the symmetry between ~.web.server
package for ~RestTestClient
and ~.web.reactive.server
for WebTestClient
, but ~.web.servlet
is an already established home for server tests with Spring MVC, and having a new package next to that creates an overlap. So, ~.web.servlet
is the "server" package, and this addition can live somewhere undertneath it as a layer over MockMvc alongside others like htmlunit
and assertj
.
It's true that RestTestClient
is for both MockMvc and for live server testing, but the same applies to WebTestClient
as well, which is in one package, and that's fine. It's basically a test client for MockMvc with the extra option to call a live server (anything that implements ClientHttpRequestsFactory
).
We have ~.web.servlet.client
where MockMvcWebTestClient
lives. That package is ideal, and rather than thinking of a new name, we can put RestTestClient
support there as the eventual successor of MockMvcWebTestClient
. In the long run there is no need for both to remain so that should eventually clear up.
This would create a conflict with new and existing AbstractMockMvcServerSpec
and ApplicationContextMockMvcSpec
classes, but it is already confusing to have those IMO, both for MockMvc, but in different packages. Instead if we drop "Mvc" from the name in the new classes, and essentially follow the same convention as in WebTestClient
, i.e. AbstractMockServerSpec
and ApplicationContextSpec
, that would give us two classes with the same name, but one for WebFlux and one for MockMvc, which would be fine.
In short my first suggstion is to move RestTestClient
into .web.servlet.client
, and rename server spec classes accordingly.
In addition I suggest to create a copy of MockMvcClientHttpRequestFactory
in the same package as RestTestClient
, and deprecate the one currently in ~.web.client
. It was added there all the way back in 3.2 because there was no better place at the time, but it has nothing to do with the rest of what's in that package, and makes sense to have it in the same package as RestTestClient
. For anyone currently using that class, this would serve as an alert that there is now higher level support and most likely no need to use that class directly. That could be described in the deprecation notice.
Thanks for the feedback. I will work on this next week. |
99afe38
to
d3c92b8
Compare
OK, done. For now I have split the work into separate commits to make it easier to review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That looks good overall now. Thanks for making those changes. I will have a more detailed look. No worries about the commits, I will squash accordingly before merging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, see some feedback on the API to create RestTestClient
instances. Let me know what you think, and if you intend to make the changes.
* {@link org.springframework.test.web.servlet.setup.MockMvcBuilders#standaloneSetup(Object...)} | ||
* to initialize {@link MockMvc}. | ||
*/ | ||
static ControllerSpec bindToController(Object... controllers) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On closer look at the MockServerSpec
hierarchy, it's reasonable that it mirrors WebTestClient
and MockMvcWebTestClient
, but MockMvc already has builders and this duplicates the API surface. The benefit is marginal IMO while it's harder to face two identical and extensive sets of options with possible slight deviations in order, naming, and Javadoc.
We could instead have a minimal spec (and single default implementation):
interface MockMvcSpec<B extends MockMvcBuilder> {
MockMvcSpec<B> configureServer(Consumer<B> consumer);
Builder configureClient();
RestTestClient build();
}
and bindTo methods:
static MockMvcSpec<StandaloneMockMvcBuilder> standaloneSetup(Object... controllers) {
StandaloneMockMvcBuilder builder = MockMvcBuilders.standaloneSetup(controllers);
return new DefaultMockMvcSpec<>(builder);
}
static MockMvcSpec<RouterFunctionMockMvcBuilder> bindToRouterFunction(RouterFunction<?>... routerFunctions) {
RouterFunctionMockMvcBuilder builder = MockMvcBuilders.routerFunctions(routerFunctions);
return new DefaultMockMvcSpec<>(builder);
}
static MockMvcSpec<DefaultMockMvcBuilder> bindToApplicationContext(WebApplicationContext context) {
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(context);
return new DefaultMockMvcSpec<>(builder);
}
static RestTestClient.Builder bindTo(MockMvc mockMvc) {
ClientHttpRequestFactory requestFactory = new MockMvcClientHttpRequestFactory(mockMvc);
return RestTestClient.bindToServer(requestFactory);
}
Setup code would then look like this:
RestTestClient client = RestTestClient.standaloneSetup(new PersonController())
.configureServer(builder -> builder
.setControllerAdvice(...)
.setValidator(...)
.configureClient()
.defaultCookie(...)
.build();
I find this a reasonable tradeoff. It makes MockMvc stand out more, increasing transparency, and making it more clear what is being configured.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking further, configureServer
would then remain as the only method that is specific toMockMvcSpec
. The rest are methods to bridge to the client Builder
. We can then just merge those together and avoid having a build with two phases that creates challenges like the one described in #35042 (comment).
So we could have justRestTestClient.Builder
with an extension for access to the MockMvc builder:
interface Builder<B extends Builder<B>> {
// ...
}
interface MockServerBuilder<M extends MockMvcBuilder> extends Builder<MockServerBuilder<M>> {
MockServerBuilder<M> configureServer(Consumer<M> consumer);
}
Then all bindTo
methods would return directly a Builder
of some kind and there would be no need for a configureClient
bridge method. This would bring an actual benefit for not duplicating the MockMvc builder hierarchy by significantly streamlining the test client setup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems reasonable. I will work on this.
Fixes spring-projectsgh-31275 Signed-off-by: Rob Worsnop <[email protected]>
Fixes spring-projectsgh-31275 Signed-off-by: Rob Worsnop <[email protected]>
Fixes spring-projectsgh-31275 Signed-off-by: Rob Worsnop <[email protected]>
Fixes spring-projectsgh-31275 Signed-off-by: Rob Worsnop <[email protected]>
Fixes spring-projectsgh-31275 Signed-off-by: Rob Worsnop <[email protected]>
d3c92b8
to
cb12db5
Compare
Done. Always nice to delete code! |
Fixes gh-31275