Skip to content

Commit 6cf0b90

Browse files
refac: Update Spring Boot from 3.5.8 to 4.0.0 (#37)
- Update Spring Boot from 3.5.8 to 4.0.0. - Update `junit-platform-launcher` from 1.12.2 to 6.0.1. - Update Springdoc from 2.8.14 to 3.0.0. - Remove dependency `org.liquibase:liquibase-core` as this is replaced by `spring-boot-starter-liquibase`. - Remove dependency `jackson-datatype-jsr310` as this is no longer necessary. - Add dependency `spring-boot-starter-restclient`. - Add dependency `spring-boot-resttestclient`. - Add dependency `org.testcontainers:junit-jupiter`. - Migrate dependency from `spring-boot-starter-web` to `spring-boot-starter-webmvc`. - Use `JsonMapper` instead of `ObjectMapper` in tests as this is the new recommended way to serialize JSON using Jackson 3+. - Rename `BaseMockMvc` to `BaseControllerTest` and start using the new `RestTestClient` from Spring Boot 4+. - Update Gradle from JDK21 to JDK25 in Dockerfiles. - Update Java from 21 to 25 in `.github/workflows/gradle-build.yml`.
1 parent f3e81f3 commit 6cf0b90

File tree

11 files changed

+153
-158
lines changed

11 files changed

+153
-158
lines changed

.github/workflows/gradle-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
uses: actions/setup-java@v5
2020
with:
2121
distribution: 'temurin'
22-
java-version: 21
22+
java-version: 25
2323

2424
- name: Use gradle/actions/setup-gradle@v4.3.0
2525
uses: gradle/actions/setup-gradle@v5.0.0

apps/gateway/build.gradle.kts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ dependencies {
1010

1111
// Spring Boot dependencies
1212
implementation(local.springboot.starter)
13-
implementation(local.springboot.starter.web)
13+
implementation(local.springboot.starter.restclient)
14+
implementation(local.springboot.starter.webmvc)
1415

1516
// Springdoc OpenAPI for providing Swagger documentation
1617
implementation(local.springdoc.openapi.starter.webmvc)
1718

18-
// Spring Boot and Testcontainers test dependencies
19+
// Spring Boot test dependencies
20+
testImplementation(local.springboot.resttestclient)
1921
testImplementation(local.springboot.starter.test)
2022

2123
// WireMock for mocking external HTTP services in tests

apps/gateway/src/main/java/com/github/thorlauridsen/service/TravelService.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,13 @@ public TravelDetails getAsync() throws InterruptedException {
7575
log.info("Fetching travel details asynchronously");
7676
val start = OffsetDateTime.now();
7777

78-
try (val scope = new StructuredTaskScope.ShutdownOnFailure()) {
78+
try (val scope = StructuredTaskScope.open()) {
7979

8080
val flightsTask = scope.fork(() -> fetchList("/flights", Flight.class));
8181
val hotelsTask = scope.fork(() -> fetchList("/hotels", Hotel.class));
8282
val carsTask = scope.fork(() -> fetchList("/rentalcars", RentalCar.class));
8383

8484
scope.join();
85-
scope.throwIfFailed(
86-
cause -> new IllegalStateException("Failed to fetch travel details", cause)
87-
);
8885

8986
val details = new TravelDetails(
9087
flightsTask.get(),

apps/gateway/src/test/java/com/github/thorlauridsen/TravelControllerTest.java

Lines changed: 29 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,36 @@
11
package com.github.thorlauridsen;
22

3-
import com.fasterxml.jackson.core.JsonProcessingException;
4-
import com.fasterxml.jackson.databind.ObjectMapper;
53
import com.github.thorlauridsen.model.TravelDetails;
64
import com.github.tomakehurst.wiremock.WireMockServer;
75
import com.github.tomakehurst.wiremock.client.WireMock;
8-
import java.nio.charset.StandardCharsets;
96
import lombok.val;
107
import org.junit.jupiter.api.AfterAll;
118
import org.junit.jupiter.api.BeforeAll;
129
import org.junit.jupiter.api.BeforeEach;
1310
import org.junit.jupiter.api.Test;
1411
import org.springframework.beans.factory.annotation.Autowired;
15-
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
12+
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureRestTestClient;
1613
import org.springframework.boot.test.context.SpringBootTest;
1714
import org.springframework.http.MediaType;
1815
import org.springframework.test.context.ActiveProfiles;
19-
import org.springframework.test.web.servlet.MockMvc;
16+
import org.springframework.test.web.servlet.client.RestTestClient;
17+
import tools.jackson.databind.json.JsonMapper;
2018

2119
import static com.github.thorlauridsen.controller.BaseEndpoint.TRAVEL_BASE_ENDPOINT;
2220
import static com.github.tomakehurst.wiremock.client.WireMock.okJson;
2321
import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
2422
import static org.junit.jupiter.api.Assertions.assertEquals;
25-
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
26-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
2723

2824
@ActiveProfiles("test")
29-
@AutoConfigureMockMvc
25+
@AutoConfigureRestTestClient
3026
@SpringBootTest
3127
class TravelControllerTest {
3228

3329
@Autowired
34-
private MockMvc mockMvc;
30+
private RestTestClient restTestClient;
3531

3632
@Autowired
37-
private ObjectMapper objectMapper;
33+
private JsonMapper jsonMapper;
3834

3935
private static final WireMockServer WIREMOCK = new WireMockServer(9561);
4036

@@ -49,50 +45,46 @@ static void stopWireMock() {
4945
}
5046

5147
@BeforeEach
52-
void resetWireMock() throws JsonProcessingException {
48+
void resetWireMock() {
5349
WIREMOCK.resetAll();
5450
setupWireMockStubs();
5551
}
5652

5753
@Test
58-
void get_travel_details_async_success() throws Exception {
59-
val response = mockMvc.perform(
60-
get(TRAVEL_BASE_ENDPOINT + "/async").accept(MediaType.APPLICATION_JSON)
61-
)
62-
.andExpect(status().isOk())
63-
.andReturn()
64-
.getResponse();
65-
66-
val json = response.getContentAsString(StandardCharsets.UTF_8);
67-
val details = objectMapper.readValue(json, TravelDetails.class);
54+
void get_travel_details_async_success() {
55+
val details = restTestClient.get()
56+
.uri(TRAVEL_BASE_ENDPOINT + "/async")
57+
.accept(MediaType.APPLICATION_JSON)
58+
.exchange()
59+
.expectStatus().isOk()
60+
.expectBody(TravelDetails.class)
61+
.returnResult()
62+
.getResponseBody();
6863

6964
assertEquals(TravelTestData.travelDetails, details);
7065
}
7166

7267
@Test
73-
void get_travel_details_sync_success() throws Exception {
74-
val response = mockMvc.perform(
75-
get(TRAVEL_BASE_ENDPOINT + "/sync").accept(MediaType.APPLICATION_JSON)
76-
)
77-
.andExpect(status().isOk())
78-
.andReturn()
79-
.getResponse();
80-
81-
val json = response.getContentAsString(StandardCharsets.UTF_8);
82-
val details = objectMapper.readValue(json, TravelDetails.class);
68+
void get_travel_details_sync_success() {
69+
val details = restTestClient.get()
70+
.uri(TRAVEL_BASE_ENDPOINT + "/sync")
71+
.accept(MediaType.APPLICATION_JSON)
72+
.exchange()
73+
.expectStatus().isOk()
74+
.expectBody(TravelDetails.class)
75+
.returnResult()
76+
.getResponseBody();
8377

8478
assertEquals(TravelTestData.travelDetails, details);
8579
}
8680

8781
/**
8882
* Setup WireMock stubs for the external services.
89-
*
90-
* @throws JsonProcessingException if there is an error processing JSON.
9183
*/
92-
void setupWireMockStubs() throws JsonProcessingException {
93-
val hotelsJson = objectMapper.writeValueAsString(TravelTestData.hotels);
94-
val flightsJson = objectMapper.writeValueAsString(TravelTestData.flights);
95-
val carsJson = objectMapper.writeValueAsString(TravelTestData.rentalCars);
84+
void setupWireMockStubs() {
85+
val hotelsJson = jsonMapper.writeValueAsString(TravelTestData.hotels);
86+
val flightsJson = jsonMapper.writeValueAsString(TravelTestData.flights);
87+
val carsJson = jsonMapper.writeValueAsString(TravelTestData.rentalCars);
9688

9789
WIREMOCK.stubFor(WireMock.get(urlEqualTo("/hotels"))
9890
.willReturn(okJson(hotelsJson)));

apps/provider/build.gradle.kts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,26 @@ dependencies {
1111

1212
// Spring Boot dependencies
1313
implementation(local.springboot.starter)
14-
implementation(local.springboot.starter.web)
14+
implementation(local.springboot.starter.webmvc)
15+
16+
// Spring Boot Liquibase dependency for database migrations
17+
implementation(local.springboot.starter.liquibase)
1518

1619
// Springdoc OpenAPI for providing Swagger documentation
1720
implementation(local.springdoc.openapi.starter.webmvc)
1821

1922
// H2 database dependency for in-memory database
2023
runtimeOnly(local.h2database)
2124

22-
// Liquibase core dependency for database migrations
23-
runtimeOnly(local.liquibase.core)
24-
2525
// PostgreSQL database driver
2626
runtimeOnly(local.postgres)
2727

2828
// Spring Boot and Testcontainers test dependencies
29-
testImplementation(local.springboot.starter.test)
30-
testImplementation(local.springboot.testcontainers)
31-
testImplementation(local.testcontainers.postgresql)
29+
testImplementation(local.springboot.resttestclient)
30+
testImplementation(local.springboot.starter.test)
31+
testImplementation(local.springboot.testcontainers)
32+
testImplementation(local.testcontainers.junit.jupiter)
33+
testImplementation(local.testcontainers.postgresql)
3234

3335
// JUnit platform launcher dependency for running JUnit tests
3436
testRuntimeOnly(local.junit.platform.launcher)

apps/provider/src/test/java/com/github/thorlauridsen/FlightControllerTest.java

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,79 @@
11
package com.github.thorlauridsen;
22

3-
import com.fasterxml.jackson.core.type.TypeReference;
4-
import com.fasterxml.jackson.databind.ObjectMapper;
53
import com.github.thorlauridsen.model.Flight;
64
import java.util.List;
75
import lombok.val;
6+
import org.jetbrains.annotations.NotNull;
87
import org.junit.jupiter.api.MethodOrderer;
98
import org.junit.jupiter.api.Order;
109
import org.junit.jupiter.api.Test;
1110
import org.junit.jupiter.api.TestMethodOrder;
1211
import org.springframework.beans.factory.annotation.Autowired;
13-
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
12+
import org.springframework.boot.resttestclient.autoconfigure.AutoConfigureRestTestClient;
1413
import org.springframework.boot.test.context.SpringBootTest;
14+
import org.springframework.core.ParameterizedTypeReference;
1515
import org.springframework.http.MediaType;
16-
import org.springframework.test.web.servlet.MockMvc;
16+
import org.springframework.test.web.servlet.client.RestTestClient;
17+
import tools.jackson.databind.json.JsonMapper;
1718

1819
import static com.github.thorlauridsen.controller.BaseEndpoint.FLIGHT_BASE_ENDPOINT;
1920
import static org.junit.jupiter.api.Assertions.assertEquals;
20-
import static org.springframework.http.HttpStatus.OK;
21-
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
22-
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
23-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
21+
import static org.junit.jupiter.api.Assertions.assertNotNull;
2422

23+
@AutoConfigureRestTestClient
2524
@SpringBootTest
26-
@AutoConfigureMockMvc
2725
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
2826
class FlightControllerTest {
2927

3028
@Autowired
31-
private MockMvc mockMvc;
29+
private RestTestClient restTestClient;
3230

3331
@Autowired
34-
private ObjectMapper objectMapper;
32+
private JsonMapper jsonMapper;
3533

3634
@Test
3735
@Order(1)
38-
void getAllFlights_success() throws Exception {
36+
void getAllFlights_success() {
3937

40-
val response = mockMvc.perform(get(FLIGHT_BASE_ENDPOINT).accept(MediaType.APPLICATION_JSON))
41-
.andExpect(status().isOk())
42-
.andReturn()
43-
.getResponse();
38+
val typeReference = new ParameterizedTypeReference<@NotNull List<Flight>>() {
39+
};
4440

45-
val json = response.getContentAsString();
46-
val flights = objectMapper.readValue(json, new TypeReference<List<Flight>>() {
47-
});
41+
val flights = restTestClient.get()
42+
.uri(FLIGHT_BASE_ENDPOINT)
43+
.accept(MediaType.APPLICATION_JSON)
44+
.exchange()
45+
.expectStatus().isOk()
46+
.expectBody(typeReference)
47+
.returnResult()
48+
.getResponseBody();
4849

49-
assertEquals(OK.value(), response.getStatus());
50+
assertNotNull(flights);
5051
assertEquals(3, flights.size());
5152
}
5253

5354
@Test
5455
@Order(2)
55-
void saveFlight_success() throws Exception {
56+
void saveFlight_success() {
5657

5758
val flight = new Flight(
5859
"XY999",
5960
"New Airline",
6061
"New Origin",
6162
"New Destination"
6263
);
63-
val json = objectMapper.writeValueAsString(flight);
64+
val json = jsonMapper.writeValueAsString(flight);
6465

65-
val response = mockMvc.perform(
66-
post(FLIGHT_BASE_ENDPOINT)
67-
.contentType(MediaType.APPLICATION_JSON)
68-
.content(json)
69-
).andExpect(status().isOk()).andReturn().getResponse();
70-
71-
assertEquals(OK.value(), response.getStatus());
72-
73-
val responseJson = response.getContentAsString();
74-
val saved = objectMapper.readValue(responseJson, Flight.class);
66+
val saved = restTestClient.post()
67+
.uri(FLIGHT_BASE_ENDPOINT)
68+
.contentType(MediaType.APPLICATION_JSON)
69+
.body(json)
70+
.exchange()
71+
.expectStatus().isOk()
72+
.expectBody(Flight.class)
73+
.returnResult()
74+
.getResponseBody();
7575

76+
assertNotNull(saved);
7677
assertEquals(flight.flightNumber(), saved.flightNumber());
7778
assertEquals(flight.airline(), saved.airline());
7879
assertEquals(flight.origin(), saved.origin());

0 commit comments

Comments
 (0)