A Spring Boot application may call another service through RestClient. A test of that outgoing call should verify the URL, HTTP method, response mapping, and the application’s handling of an error response. @RestClientTest loads a focused REST client test context and provides MockRestServiceServer to answer the request without calling the remote service. The test exercises the real client code, while the server’s response is controlled by the test.
1. What @RestClientTest Loads
A REST client test is a slice test: Spring Boot creates the selected client component and the configuration needed for its HTTP and JSON work. It does not load every service, repository, or controller in the application. Select the class under test explicitly so the boundary is visible:
@RestClientTest(ShippingClient.class)
class ShippingClientTest {
@Autowired ShippingClient client;
@Autowired MockRestServiceServer server;
}
The injected server is bound to the Boot-configured RestClient.Builder used by ShippingClient. When the client sends a request, the mock server checks the expected request and returns the response arranged by the test. No remote HTTP connection is opened. This is useful for checking client behavior quickly, but it does not test DNS, TLS, connection timeouts, or the real shipping API. An incoming endpoint has a different boundary; see the Spring Boot REST controller testing guide for that case. For those transport details, use a mock HTTP server with a real client.
2. Create the Shipping Client
An order application asks a shipping provider for the latest status of a parcel. The provider exposes GET /api/shipments/{trackingId} and returns JSON containing the tracking ID and status. ShippingClient converts that response into a ShipmentStatus record. A missing shipment is represented by Optional.empty() rather than a failed order workflow.
Add the blocking REST client starter, JSON support, and the matching test starter to a Spring Boot 4.1.2 Maven project:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-restclient-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
The response type has the same field names as the JSON document. The example uses a record so the mapping and test assertion remain small:
public record ShipmentStatus(String trackingId, String status) { }
The client receives Boot’s configured RestClient.Builder through its constructor. It sets the provider’s base URL once, then supplies the shipment path and tracking ID for each request. retrieve() reads the response and body() converts the JSON to ShipmentStatus.
@Component
public class ShippingClient {
private final RestClient restClient;
public ShippingClient(RestClient.Builder builder) {
this.restClient = builder.baseUrl("https://shipping.example/api").build();
}
public Optional<ShipmentStatus> statusOf(String trackingId) {
try {
ShipmentStatus status = restClient.get()
.uri("/shipments/{trackingId}", trackingId)
.retrieve().body(ShipmentStatus.class);
return Optional.ofNullable(status);
} catch (HttpClientErrorException.NotFound ex) {
return Optional.empty();
}
}
}
Only a 404 becomes an empty result. Other HTTP errors still reach the caller as exceptions, so a temporary provider failure cannot be mistaken for an unknown tracking ID. The https://shipping.example address is a placeholder for the application feature; the test below intercepts it and never contacts that host.
3. Test a Successful Response
The first test arranges a JSON response for tracking ID TRK-42. It also requires a GET request to the full URL. With a RestClient.Builder, the expectation includes the base URL; using only /shipments/TRK-42 would not match the request.
@RestClientTest(ShippingClient.class)
class ShippingClientTest {
@Autowired ShippingClient client;
@Autowired MockRestServiceServer server;
@Test
void mapsShipmentStatus() {
server.expect(requestTo("https://shipping.example/api/shipments/TRK-42"))
.andExpect(method(HttpMethod.GET))
.andRespond(withSuccess("""
{"trackingId":"TRK-42","status":"IN_TRANSIT"}
""", MediaType.APPLICATION_JSON));
assertThat(client.statusOf("TRK-42"))
.contains(new ShipmentStatus("TRK-42", "IN_TRANSIT"));
server.verify();
}
}
Calling statusOf() executes the production client method. The assertion confirms that its result contains the values supplied in the JSON response, so it checks response conversion as well as the client method’s return value. The mock server checks the URL and method. server.verify() confirms that the expected exchange actually occurred; an arranged response that was never requested would not count as a passed client test.
4. Test a Missing Shipment
The shipping provider may return HTTP 404 when it has no record for a tracking ID. The next test sends that response and checks the behavior chosen in statusOf(): an empty Optional. This is different from a malformed JSON response or an unavailable provider.
@Test
void returnsEmptyWhenShipmentIsMissing() {
server.expect(requestTo("https://shipping.example/api/shipments/TRK-99"))
.andExpect(method(HttpMethod.GET))
.andRespond(withResourceNotFound());
assertThat(client.statusOf("TRK-99")).isEmpty();
server.verify();
}
withResourceNotFound() produces the 404 response. RestClient.retrieve() raises HttpClientErrorException.NotFound, which the client catches and maps to Optional.empty(). The test therefore verifies the application’s error policy, not just that the mock server can produce an error.
5. Run the Tests and Choose the Next Test Boundary
Run the complete companion project with Java 17 or later. Maven should report two passing tests; neither test requires a running shipping service.
mvn test
These tests cover request construction, JSON mapping, and the selected 404 policy. They do not prove that the real provider accepts the URL, returns the documented schema, or behaves correctly over the network. The Spring Framework client-testing guide linked above explains why a mock web server is appropriate when transport behavior matters. A contract or staging test can separately confirm the provider’s actual response shape.
6. FAQs
6.1. Does @RestClientTest start the application server?
No. The test creates a focused application context for the selected outgoing client and supplies a mock response source. It does not start an embedded web server or call the remote provider. Use SpringBootTest when the behavior under test needs the full application context; use a mock HTTP server when the actual HTTP client transport must run.
6.2. Why must the expectation contain the full URL?
The example builds RestClient with a base URL. Spring Boot’s REST client test reference specifies that MockRestServiceServer expectations for a RestClient.Builder use the full URI. That is why the test expects https://shipping.example/api/shipments/TRK-42, rather than only the path.
6.3. Can I test a RestTemplate client with this annotation?
Yes. The slice also supports RestTemplateBuilder. Keep a test focused on the HTTP client your production code actually uses; the expectation format for a RestTemplateBuilder with a root URI differs from the RestClient.Builder example above. Check the builder-specific examples in the Spring Boot reference linked above before copying expectations between them.
7. Conclusion
Use RestClientTest when you need a fast test of an outgoing Spring REST client with controlled HTTP responses. Verify the requested URL and method, then assert the mapped success and error results. Add a real HTTP mock server test when network behavior is part of the requirement.
Hi Sir, I have other custom class dependency in my server class.How to resolve that. I was Mock that dependency but I am getting unsatisfieddependency exception.
Consider using @MockBean