Files
spring-boot-demo/rest-client-test/README.md
T

5.2 KiB

rest-client-test

Companion module for ankurm.com - testing Spring REST clients on Spring Boot 4.1.1 with @RestClientTest and MockRestServiceServer: a RestClient-based client, a RestTemplate-based client, a declarative @HttpExchange interface client, an error-path test, and @MockitoBean standing in for a plain collaborator bean now that @MockBean is gone.

Versions actually used here

Component Version Verified against
Spring Boot 4.1.1 spring-boot-starter-parent POM, Maven Central
Spring Framework 7.0.9 resolved transitively, confirmed via mvn dependency:tree
JDK 25 (Temurin 25.0.4.1+1) java -version in this sandbox
JUnit 6.0.3 resolved transitively
Jackson 3.1.5, groupId tools.jackson.core resolved transitively, confirmed via mvn dependency:tree and by unzipping the jar

The module-naming trap, reconfirmed

@RestClientTest and MockRestServiceServer's auto-configuration now live in spring-boot-restclient-test (org.springframework.boot.restclient.test.autoconfigure.RestClientTest), not in spring-boot-resttestclient - that second module backs the newer RestTestClient type used in the rest-test-client module instead. Confirmed by unzipping both jars and listing their classes; the names are one letter apart and Maven Central's search box will happily suggest the wrong one.

RestTemplateBuilder moved too, from org.springframework.boot.web.client (Boot 3.x) to org.springframework.boot.restclient.RestTemplateBuilder - same module, spring-boot-restclient, as RestClient.Builder's auto-configuration. A Boot 3 import of RestTemplateBuilder fails to compile on Boot 4.1 with no hint beyond "package does not exist."

The bigger surprise: Jackson 3 by default

mvn dependency:tree resolves tools.jackson.core:jackson-databind:3.1.5, not com.fasterxml.jackson.core:jackson-databind. Spring Boot 4.1's spring-boot-starter-jackson pulls in Jackson 3, whose ObjectMapper lives at tools.jackson.databind.ObjectMapper - a different package entirely, not just a version bump. com.fasterxml.jackson.core:jackson-annotations is still on the classpath (so @JsonProperty etc. keep their old import), but jackson-databind and jackson-core are the new tools.jackson.* artifacts. Every test class in this module imports tools.jackson.databind.ObjectMapper, not the classic one - this bit every test file on the first compile attempt here. See the Jackson 3 post on this blog for the full migration story.

Quickstart

./scripts/run-all.sh

Runs the full suite and regenerates every file in docs/output/.

Test classes

Class What it proves
RestClientEmployeeTest @RestClientTest(EmployeeClient.class) + MockRestServiceServer, with @MockitoBean replacing the collaborator EmployeeAuditLogger
RestTemplateEmployeeTest The same job on RestTemplate, and the one case where a relative requestTo() still works against an absolute root URI
HttpInterfaceClientTest @RestClientTest(components = AppConfig.class) wiring a declarative @HttpExchange interface client through the mock
ErrorScenarioTest A 404 from MockRestServiceServer surfacing as HttpClientErrorException.NotFound

Captured output

File From
00-full-test-run.txt mvn test, all 4 classes, green
01-restclient-basic-fetch.txt RestClientEmployeeTest
02-resttemplate-same-mocking-code.txt RestTemplateEmployeeTest
03-404-throws-notfound.txt ErrorScenarioTest
04-http-interface-client.txt HttpInterfaceClientTest
05-restclient-relative-requestto-fails.txt A scratch test (not committed) reproducing the real AssertionError when requestTo() is written relative against a RestClient built with an absolute baseUrl

Why #01 and #05 use an absolute requestTo() but #02 does not

EmployeeClient builds its RestClient with RestClient.Builder.baseUrl("http://hr-api.example.com"). EmployeeTemplateClient builds its RestTemplate with RestTemplateBuilder.rootUri(...) instead. Spring Boot's MockRestServiceServerAutoConfiguration detects a RestTemplate built with a root URI and binds it through RootUriRequestExpectationManager (org.springframework.boot.restclient.test.RootUriRequestExpectationManager), which strips the root URI before matching - so requestTo("/employees/2") still works. There is no equivalent for RestClient.Builder.baseUrl(...): a RestClient test has to match the full absolute URI, as #05's captured failure shows. This is covered in depth in the published post.