Skip to content

Latest commit

 

History

History
227 lines (169 loc) · 4.63 KB

File metadata and controls

227 lines (169 loc) · 4.63 KB

@RestClientTest

Testing REST clients in isolation with MockRestServiceServer.

Overview

@RestClientTest auto-configures:

  • RestTemplate/RestClient with mock server support
  • Jackson ObjectMapper
  • MockRestServiceServer

Basic Setup

@RestClientTest(WeatherService.class)
class WeatherServiceTest {
  
  @Autowired
  private WeatherService weatherService;
  
  @Autowired
  private MockRestServiceServer server;
}

Testing RestTemplate

@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");
  }
}

Testing RestClient (Spring 6.1+)

@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);
  }
}

Request Matching

Exact URL

server.expect(requestTo("https://api.example.com/users/1"))
  .andRespond(withSuccess());

URL Pattern

server.expect(requestTo(matchesPattern("https://api.example.com/users/\\d+")))
  .andRespond(withSuccess());

HTTP Method

server.expect(ExpectedCount.once(), 
  requestTo("https://api.example.com/users"))
  .andExpect(method(HttpMethod.POST))
  .andRespond(withCreatedEntity(URI.create("/users/1")));

Request Body

server.expect(requestTo("https://api.example.com/users"))
  .andExpect(content().contentType(MediaType.APPLICATION_JSON))
  .andExpect(content().json("{\"name\": \"John\"}"))
  .andRespond(withSuccess());

Headers

server.expect(requestTo("https://api.example.com/users"))
  .andExpect(header("Authorization", "Bearer token123"))
  .andExpect(header("X-Api-Key", "secret"))
  .andRespond(withSuccess());

Response Types

Success with Body

server.expect(requestTo("/users/1"))
  .andRespond(withSuccess()
    .contentType(MediaType.APPLICATION_JSON)
    .body("{\"id\": 1, \"name\": \"John\"}"));

Success from Resource

server.expect(requestTo("/users/1"))
  .andRespond(withSuccess()
    .body(new ClassPathResource("user-response.json")));

Created

server.expect(requestTo("/users"))
  .andExpect(method(HttpMethod.POST))
  .andRespond(withCreatedEntity(URI.create("/users/1")));

Error Response

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\"}"));

Verifying Requests

@Test
void shouldCallApi() {
  server.expect(ExpectedCount.once(), 
    requestTo("https://api.example.com/data"))
    .andRespond(withSuccess());
  
  service.fetchData();
  
  server.verify(); // Verify all expectations met
}

Ignoring Extra Requests

@Test
void shouldHandleMultipleCalls() {
  server.expect(ExpectedCount.manyTimes(),
    requestTo(matchesPattern("/api/.*")))
    .andRespond(withSuccess());
  
  // Multiple calls allowed
  service.callApi();
  service.callApi();
  service.callApi();
}

Reset Between Tests

@BeforeEach
void setUp() {
  server.reset();
}

Testing Timeouts

server.expect(requestTo("/slow-endpoint"))
  .andRespond(withSuccess()
    .body("{\"data\": \"test\"}")
    .delay(100, TimeUnit.MILLISECONDS));

// Test timeout handling

Best Practices

  1. Always verify server.verify() at end of test
  2. Use resource files for large JSON responses
  3. Match on minimal set of request attributes
  4. Reset server in @BeforeEach
  5. Test error responses, not just success
  6. Verify request body for POST/PUT calls