Merge branch '4.0.x' into 4.1.x

Closes gh-51121
This commit is contained in:
Stéphane Nicoll
2026-07-24 15:19:14 +02:00
24 changed files with 292 additions and 230 deletions
@@ -18,7 +18,6 @@ package smoketest.session;
import java.net.URI;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -26,24 +25,24 @@ import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder;
import org.springframework.boot.http.client.HttpClientSettings;
import org.springframework.boot.http.client.HttpRedirects;
import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.resttestclient.TestRestTemplate;
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureTestRestTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -62,9 +61,6 @@ class SampleSessionJdbcApplicationTests {
private static final HttpClientSettings DONT_FOLLOW_REDIRECTS = HttpClientSettings.defaults()
.withRedirects(HttpRedirects.DONT_FOLLOW);
@Autowired
private RestTemplateBuilder restTemplateBuilder;
@Autowired
private TestRestTemplate restTemplate;
@@ -86,15 +82,18 @@ class SampleSessionJdbcApplicationTests {
}
private @Nullable String performLogin() {
RestTemplate restTemplate = this.restTemplateBuilder.clientSettings(DONT_FOLLOW_REDIRECTS).build();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.TEXT_HTML));
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
ClientHttpRequestFactory requestFactory = ClientHttpRequestFactoryBuilder.detect().build(DONT_FOLLOW_REDIRECTS);
RestClient restClient = RestClient.builder().requestFactory(requestFactory).build();
MultiValueMap<String, String> form = new LinkedMultiValueMap<>();
form.set("username", "user");
form.set("password", "password");
ResponseEntity<String> entity = restTemplate.exchange("http://localhost:" + this.port + "/login",
HttpMethod.POST, new HttpEntity<>(form, headers), String.class);
ResponseEntity<String> entity = restClient.post()
.uri("http://localhost:" + this.port + "/login")
.accept(MediaType.TEXT_HTML)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(form)
.retrieve()
.toEntity(String.class);
return entity.getHeaders().getFirst("Set-Cookie");
}
@@ -20,12 +20,11 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import smoketest.test.domain.VehicleIdentificationNumber;
import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.client.RestClient;
/**
* {@link VehicleDetailsService} backed by a remote REST service.
@@ -37,10 +36,10 @@ public class RemoteVehicleDetailsService implements VehicleDetailsService {
private static final Log logger = LogFactory.getLog(RemoteVehicleDetailsService.class);
private final RestTemplate restTemplate;
private final RestClient restClient;
public RemoteVehicleDetailsService(ServiceProperties properties, RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.baseUri(properties.getVehicleServiceRootUrl()).build();
public RemoteVehicleDetailsService(ServiceProperties properties, RestClient.Builder restTemplateBuilder) {
this.restClient = restTemplateBuilder.baseUrl(properties.getVehicleServiceRootUrl()).build();
}
@Override
@@ -49,8 +48,10 @@ public class RemoteVehicleDetailsService implements VehicleDetailsService {
Assert.notNull(vin, "'vin' must not be null");
logger.debug("Retrieving vehicle data for: " + vin);
try {
VehicleDetails response = this.restTemplate.getForObject("/vehicle/{vin}/details", VehicleDetails.class,
vin);
VehicleDetails response = this.restClient.get()
.uri("/vehicle/{vin}/details", vin)
.retrieve()
.body(VehicleDetails.class);
Assert.state(response != null, "'response' must not be null");
return response;
}
@@ -17,6 +17,7 @@
package smoketest.test.service;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import smoketest.test.domain.VehicleIdentificationNumber;
import org.springframework.beans.factory.annotation.Autowired;
@@ -25,7 +26,9 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.test.web.client.RequestMatcher;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -39,17 +42,27 @@ import static org.springframework.test.web.client.response.MockRestResponseCreat
* Tests for {@link RemoteVehicleDetailsService}.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
@RestClientTest({ RemoteVehicleDetailsService.class, ServiceProperties.class })
@RestClientTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class RemoteVehicleDetailsServiceTests {
private static final String VIN = "00000000000000000";
@Autowired
private RemoteVehicleDetailsService service;
private static final String BASE_URL = "https://api.example.com";
@Autowired
private MockRestServiceServer server;
private final RemoteVehicleDetailsService service;
private final MockRestServiceServer server;
RemoteVehicleDetailsServiceTests(@Autowired RestClient.Builder restClientBuilder,
@Autowired MockRestServiceServer server) {
ServiceProperties properties = new ServiceProperties();
properties.setVehicleServiceRootUrl(BASE_URL);
this.service = new RemoteVehicleDetailsService(properties, restClientBuilder);
this.server = server;
}
@Test
@SuppressWarnings("NullAway") // Test null check
@@ -60,7 +73,7 @@ class RemoteVehicleDetailsServiceTests {
@Test
void getVehicleDetailsWhenResultIsSuccessShouldReturnDetails() {
this.server.expect(requestTo("/vehicle/" + VIN + "/details"))
this.server.expect(prepareRequest("/vehicle/" + VIN + "/details"))
.andRespond(withSuccess(getClassPathResource("vehicledetails.json"), MediaType.APPLICATION_JSON));
VehicleDetails details = this.service.getVehicleDetails(new VehicleIdentificationNumber(VIN));
assertThat(details.getMake()).isEqualTo("Honda");
@@ -69,18 +82,22 @@ class RemoteVehicleDetailsServiceTests {
@Test
void getVehicleDetailsWhenResultIsNotFoundShouldThrowException() {
this.server.expect(requestTo("/vehicle/" + VIN + "/details")).andRespond(withStatus(HttpStatus.NOT_FOUND));
this.server.expect(prepareRequest("/vehicle/" + VIN + "/details")).andRespond(withStatus(HttpStatus.NOT_FOUND));
assertThatExceptionOfType(VehicleIdentificationNumberNotFoundException.class)
.isThrownBy(() -> this.service.getVehicleDetails(new VehicleIdentificationNumber(VIN)));
}
@Test
void getVehicleDetailsWhenResultIServerErrorShouldThrowException() {
this.server.expect(requestTo("/vehicle/" + VIN + "/details")).andRespond(withServerError());
this.server.expect(prepareRequest("/vehicle/" + VIN + "/details")).andRespond(withServerError());
assertThatExceptionOfType(HttpServerErrorException.class)
.isThrownBy(() -> this.service.getVehicleDetails(new VehicleIdentificationNumber(VIN)));
}
private static RequestMatcher prepareRequest(String path) {
return requestTo(BASE_URL + path);
}
private ClassPathResource getClassPathResource(String path) {
return new ClassPathResource(path, getClass());
}