Spring Boot 中的內置測試容器支持
1. 概述
在本教程中,我們將討論 Spring Boot 3.1 中引入的增強的 Testcontainers 支持。
此更新提供了一種更簡化的容器配置方法,它允許我們出於本地開發目的啟動它們。因此,使用測試容器開發和運行測試成為一個無縫且高效的過程。
2. SpringBoot 3.1之前的測試容器
我們可以使用 Testcontainers 在測試階段創建類似生產的環境。通過這樣做,我們將消除對模擬的需求,並編寫不與實現細節耦合的高質量自動化測試。
對於本文中的代碼示例,我們將使用一個簡單的 Web 應用程序,其中包含 MongoDB 數據庫作為持久層和一個小型 REST 接口:
@RestController
@RequestMapping("characters")
public class MiddleEarthCharactersController {
private final MiddleEarthCharactersRepository repository;
// constructor not shown
@GetMapping
public List<MiddleEarthCharacter> findByRace(@RequestParam String race) {
return repository.findAllByRace(race);
}
@PostMapping
public MiddleEarthCharacter save(@RequestBody MiddleEarthCharacter character) {
return repository.save(character);
}
}
在集成測試期間,我們將啟動一個包含數據庫服務器的 Docker 容器。由於容器暴露的數據庫端口將被動態分配,因此我們無法在屬性文件中定義數據庫URL。因此,對於 3.1 之前版本的 Spring Boot 應用程序,我們需要使用@DynamicPropertySource
註解,以便將這些屬性添加到DynamicPropertyRegistry
:
@Testcontainers
@SpringBootTest(webEnvironment = DEFINED_PORT)
class DynamicPropertiesIntegrationTest {
@Container
static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
@DynamicPropertySource
static void setProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl);
}
// ...
}
對於集成測試,我們將使用@SpringBootTest
註釋在配置文件中定義的端口上啟動應用程序。此外,我們將使用 Testcontainers 來設置環境。
最後,讓我們使用 REST-assured 來執行 HTTP 請求並斷言響應的有效性:
@Test
void whenRequestingHobbits_thenReturnFrodoAndSam() {
repository.saveAll(List.of(
new MiddleEarthCharacter("Frodo", "hobbit"),
new MiddleEarthCharacter("Samwise", "hobbit"),
new MiddleEarthCharacter("Aragon", "human"),
new MiddleEarthCharacter("Gandalf", "wizzard")
));
when().get("/characters?race=hobbit")
.then().statusCode(200)
.and().body("name", hasItems("Frodo", "Samwise"));
}
3.使用@ServiceConnection
作為動態屬性
從SpringBoot 3.1開始,我們可以利用@ServiceConnection
註解來消除定義動態屬性的樣板代碼。
首先,我們需要在pom.xml
中包含spring-boot-testcontainers
依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
之後,我們可以刪除註冊所有動態屬性的靜態方法。相反,我們只需使用@ServiceConnection
註釋容器:
@Testcontainers
@SpringBootTest(webEnvironment = DEFINED_PORT)
class ServiceConnectionIntegrationTest {
@Container
@ServiceConnection
static MongoDBContainer mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
// ...
}
@ServiceConncetion
允許 SpringBoot 的自動配置動態註冊所有需要的屬性。在幕後, @ServiceConncetion
根據容器類或 Docker 映像名稱確定需要哪些屬性。
所有支持該註解的容器和鏡像的列表可以在Spring Boot的官方文檔中找到。
4. 測試容器對本地開發的支持
另一個令人興奮的功能是以最少的配置將測試容器無縫集成到本地開發中。此功能使我們不僅可以在測試期間複製生產環境,而且還可以用於本地開發。
為了啟用它,我們首先需要創建一個TestConfiguration
並將所有Testcontainers聲明為Spring Bean。我們還添加@ServiceConnection
註釋,它將應用程序無縫綁定到數據庫:
@TestConfiguration(proxyBeanMethods = false)
class LocalDevTestcontainersConfig {
@Bean
@ServiceConnection
public MongoDBContainer mongoDBContainer() {
return new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
}
}
由於所有Testcontainers
依賴項都是通過test
範圍導入的,因此我們需要從test
包啟動應用程序。因此,讓我們在這個包中創建一個main()
方法,該方法從java
包中調用實際的main()
方法:
public class LocalDevApplication {
public static void main(String[] args) {
SpringApplication.from(Application::main)
.with(LocalDevTestcontainersConfig.class)
.run(args);
}
}
就是這個。現在我們可以從這個main()
方法本地啟動應用程序,它將使用 MongoDB 數據庫。
讓我們從 Postman 發送一個 POST 請求,然後直接連接到數據庫並檢查數據是否正確持久化:
為了連接到數據庫,我們需要找到容器公開的端口。我們可以從應用程序日誌中獲取它,或者只需運行docker ps
命令即可:
最後,我們可以使用 MongoDB 客戶端通過 URL mongodb://localhost:63437/test
連接數據庫,並查詢characters
集合:
就這樣,我們可以連接並查詢 Testcontainer 啟動的數據庫進行本地開發。
5. 與 DevTools 和@RestartScope
集成
如果我們在本地開發期間經常重新啟動應用程序,則潛在的缺點是每次都會重新啟動所有容器。因此,啟動速度可能會變慢,並且測試數據將會丟失。
但是,通過利用 Testcontainers 與spring-boot-devtools
集成,我們可以在應用程序關閉時使容器保持活動狀態。這是一個實驗性的 Testcontainers 功能,可實現更流暢、更高效的開發體驗,因為它節省了寶貴的時間和測試數據。
讓我們首先添加spring-boot-devtools
依賴項:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
現在,我們可以返回到本地開發的測試配置,並使用@RestartScope
註解來註解 Testcontainers bean:
@Bean
@RestartScope
@ServiceConnection
public MongoDBContainer mongoDBContainer() {
return new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"));
}
或者,我們可以使用 Testcontainer API 的withReuse(true)
方法:
@Bean
@ServiceConnection
public MongoDBContainer mongoDBContainer() {
return new MongoDBContainer(DockerImageName.parse("mongo:4.0.10"))
.withReuse(true);
}
因此,我們現在可以從test
包的main()
方法啟動應用程序,並利用spring-boot-devtools
實時重新加載功能。例如,我們可以保存來自 Postman 的條目,然後重新編譯並重新加載應用程序:
讓我們進行一些小的更改,例如將請求映射從“characters”
切換到“api/characters”
並重新編譯:
我們已經可以從應用程序日誌或 Docker 本身看到數據庫容器沒有重新啟動。儘管如此,讓我們更進一步,檢查應用程序在重新啟動後是否重新連接到同一個數據庫。例如,我們可以通過在新路徑發送 GET 請求並期望之前插入的數據在那裡來實現這一點:
六,結論
在本文中,我們討論了 SpringBoot 3.1 的新 Testcontainers 功能。我們學習瞭如何使用新的@ServiceConnection
註釋,該註釋提供了使用@DynamicPropertySource
和样板配置的簡化替代方案。
接下來,我們通過在test
包中創建一個額外的main()
方法並將它們聲明為 Spring bean,深入研究如何利用 Testcontainers 進行本地開發。除此之外,與spring-boot-devtools
和@RestartScope
集成使我們能夠為本地開發創建一個快速、一致且可靠的環境。
與往常一樣,本文中使用的完整代碼可以在 GitHub 上找到。