Skip to content

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

Conversation

rworsnop
Copy link

Fixes gh-31275

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Feb 14, 2025
@rworsnop rworsnop force-pushed the rest-test-client branch 3 times, most recently from 3d73111 to 99c28f0 Compare February 19, 2025 18:33
@bclozel bclozel added the in: web Issues in web modules (web, webmvc, webflux, websocket) label Jun 3, 2025
@sbrannen sbrannen added the in: test Issues in the test module label Jun 17, 2025
@bclozel bclozel added type: enhancement A general enhancement and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jun 17, 2025
@bclozel bclozel added this to the 7.0.x milestone Jun 17, 2025
@rstoyanchev rstoyanchev self-assigned this Jul 18, 2025
@rstoyanchev rstoyanchev modified the milestones: 7.0.x, 7.0.0-M8 Jul 18, 2025
Copy link
Contributor

@rstoyanchev rstoyanchev left a 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.

@rworsnop
Copy link
Author

Thanks for the feedback. I will work on this next week.

@rworsnop rworsnop requested a review from rstoyanchev July 22, 2025 18:01
@rworsnop
Copy link
Author

OK, done. For now I have split the work into separate commits to make it easier to review.

Copy link
Contributor

@rstoyanchev rstoyanchev left a 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.

Copy link
Contributor

@rstoyanchev rstoyanchev left a 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) {
Copy link
Contributor

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.

Copy link
Contributor

@rstoyanchev rstoyanchev Jul 23, 2025

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.

Copy link
Author

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.

@rworsnop rworsnop requested a review from rstoyanchev July 23, 2025 23:42
@rworsnop
Copy link
Author

Done. Always nice to delete code!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: test Issues in the test module in: web Issues in web modules (web, webmvc, webflux, websocket) type: enhancement A general enhancement
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Introduce an alternative to WebTestClient based on RestClient
5 participants