Testing REST clients in isolation with MockRestServiceServer.
@RestClientTest auto-configures:
- RestTemplate/RestClient with mock server support
- Jackson ObjectMapper
- MockRestServiceServer
@RestClientTest(WeatherService.class)
class WeatherServiceTest {
@Autowired
private WeatherService weatherService;
@Autowired
private MockRestServiceServer server;
}@RestClientTest(WeatherService.class)
class WeatherServiceTest {
@Autowired
private WeatherService weatherService;
@Autowired
private MockRestServiceServer server;
@Test
void shouldFetchWeather() {
// Given
server.expect(requestTo("https://api.weather.com/v1/current"))
.andExpect(method(HttpMethod.GET))
.andExpect(queryParam("city", "Berlin"))
.andRespond(withSuccess()
.contentType(MediaType.APPLICATION_JSON)
.body("{\"temperature\": 22, \"condition\": \"Sunny\"}"));
// When
Weather weather = weatherService.getCurrentWeather("Berlin");
// Then
assertThat(weather.getTemperature()).isEqualTo(22);
assertThat(weather.getCondition()).isEqualTo("Sunny");
}
}@RestClientTest(WeatherService.class)
class WeatherServiceTest {
@Autowired
private WeatherService weatherService;
@Autowired
private MockRestServiceServer server;
@Test
void shouldFetchWeatherWithRestClient() {
server.expect(requestTo("https://api.weather.com/v1/current"))
.andRespond(withSuccess()
.body("{\"temperature\": 22}"));
Weather weather = weatherService.getCurrentWeather("Berlin");
assertThat(weather.getTemperature()).isEqualTo(22);
}
}server.expect(requestTo("https://api.example.com/users/1"))
.andRespond(withSuccess());server.expect(requestTo(matchesPattern("https://api.example.com/users/\\d+")))
.andRespond(withSuccess());server.expect(ExpectedCount.once(),
requestTo("https://api.example.com/users"))
.andExpect(method(HttpMethod.POST))
.andRespond(withCreatedEntity(URI.create("/users/1")));server.expect(requestTo("https://api.example.com/users"))
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(content().json("{\"name\": \"John\"}"))
.andRespond(withSuccess());server.expect(requestTo("https://api.example.com/users"))
.andExpect(header("Authorization", "Bearer token123"))
.andExpect(header("X-Api-Key", "secret"))
.andRespond(withSuccess());server.expect(requestTo("/users/1"))
.andRespond(withSuccess()
.contentType(MediaType.APPLICATION_JSON)
.body("{\"id\": 1, \"name\": \"John\"}"));server.expect(requestTo("/users/1"))
.andRespond(withSuccess()
.body(new ClassPathResource("user-response.json")));server.expect(requestTo("/users"))
.andExpect(method(HttpMethod.POST))
.andRespond(withCreatedEntity(URI.create("/users/1")));server.expect(requestTo("/users/999"))
.andRespond(withResourceNotFound());
server.expect(requestTo("/users"))
.andRespond(withServerError()
.body("Internal Server Error"));
server.expect(requestTo("/users"))
.andRespond(withStatus(HttpStatus.BAD_REQUEST)
.body("{\"error\": \"Invalid input\"}"));@Test
void shouldCallApi() {
server.expect(ExpectedCount.once(),
requestTo("https://api.example.com/data"))
.andRespond(withSuccess());
service.fetchData();
server.verify(); // Verify all expectations met
}@Test
void shouldHandleMultipleCalls() {
server.expect(ExpectedCount.manyTimes(),
requestTo(matchesPattern("/api/.*")))
.andRespond(withSuccess());
// Multiple calls allowed
service.callApi();
service.callApi();
service.callApi();
}@BeforeEach
void setUp() {
server.reset();
}server.expect(requestTo("/slow-endpoint"))
.andRespond(withSuccess()
.body("{\"data\": \"test\"}")
.delay(100, TimeUnit.MILLISECONDS));
// Test timeout handling- Always verify
server.verify()at end of test - Use resource files for large JSON responses
- Match on minimal set of request attributes
- Reset server in @BeforeEach
- Test error responses, not just success
- Verify request body for POST/PUT calls