1. Einführung
In diesem Tutorial werden wir uns ansehen, wie wir testcontainers in einer Spring Boot-Anwendung verwenden können, um Integrationstests zu erstellen.
Testcontainers sind leichtgewichtige, wegwerfbare Instanzen von Infrastrukturdiensten wie Datenbanken, Message Brokers usw., die in Docker-Containern laufen können. Wenn wir Integrationstests schreiben, möchten wir diese Dienste zur Verfügung haben, anstatt sie zu mocken oder das Setup und Teardown mit benutzerdefinierten, fehleranfälligen Skripten zu handhaben.
Lassen Sie uns sehen, wie wir einen Datenbank-Container mit Testcontainers einrichten können.
2. Voraussetzungen
Zuerst müssen wir unser Projekt einrichten, um mit Testcontainers zu arbeiten. Wir können Docker von der offiziellen Website herunterladen und installieren: https://www.docker.com/get-started
Als Nächstes müssen wir die Testcontainers-Abhängigkeiten in unsere pom.xml-Datei hinzufügen:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
</dependency>
<dependency
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.19.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>1.19.7</version>
<scope>test</scope>
</dependency>
Stellen wir sicher, dass wir die neueste Version der Testcontainers-Abhängigkeiten junit-jupiter und testcontainers-postgresql verwenden. Ebenso sollten wir die korrekte Version für spring-boot-testcontainers nutzen, da einige Klassen in älteren Versionen von Spring Boot möglicherweise nicht verfügbar sind.
3. Verwendung von Testcontainers in Spring Boot Tests
Spring Boot unterstützt schon seit einiger Zeit Testcontainers. Mit der Veröffentlichung von Spring Boot 3.1 wurde die Integration jedoch weiter verbessert. Schauen wir uns an, wie der alte Ansatz mit dem neuen verglichen werden kann.
In Versionen vor Spring Boot 3.1 mussten wir Spring Boot manuell konfigurieren, um eine Verbindung zu den innerhalb der Container laufenden Diensten herzustellen. Dies führte oft dazu, dass wir etwas Boilerplate-Code einfügten, der die Verwendung der @DynamicPropertySource-Annotation beinhaltete, um die notwendigen Eigenschaften festzulegen:
@SpringBootTest
@Testcontainers
class MyIntegrationTests {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
@Test
void myTest() {
// ...
}
@DynamicPropertySource
static void postgresProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
}
Oben haben wir den Test mit @Testcontainer annotiert, um der Testklasse mitzuteilen, dass sie Testcontainers verwendet. Wir haben dann eine Instanz des PostgreSQL-Containers mithilfe der @Container-Annotation definiert. Dies wird einen PostgreSQL-Container mit der angegebenen Version starten. Normalerweise hätten wir die Verbindungseigenschaften in der application.properties-Datei konfiguriert. Da wir jedoch Testcontainers verwenden, wissen wir noch nicht, wie die Verbindungseigenschaften aussehen werden. Daher nutzen wir @DynamicPropertySource, um die Verbindungseigenschaften von der Container-Instanz abzurufen und sie im Spring Boot-Anwendungskontext zu setzen.
Wie wir sehen können, beinhaltet dieser Ansatz einige bewegliche Teile und ist selbst für einen einfachen Test sehr ausführlich.
Ab Spring Boot 3.1 können wir die @ServiceConnection-Annotation verwenden, um den Prozess zu vereinfachen:
@SpringBootTest
@Testcontainers
class MyIntegrationTests {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16");
@Test
void myTest() {
// ...
}
}
Hier haben wir die @DynamicPropertySource-Annotation durch @ServiceConnection ersetzt. Dies beseitigt die Notwendigkeit, sich alle Eigenschaften für einen bestimmten Dienst zu merken, und ermöglicht es Spring Boot, die Verbindungskonfiguration automatisch zu handhaben. Im Detail funktioniert es, indem der Typ des Containers entdeckt wird und ein entsprechendes ConnectionDetails-Bean erstellt wird. Anschließend verwendet Spring Boot dies, um die Verbindung zu dem im Testcontainer laufenden Dienst zu konfigurieren.
Wir können alle unterstützten Container in der offiziellen Spring Boot-Dokumentation nachschlagen.
4. Verwendung von Testcontainers während der Entwicklung
Lassen Sie uns einen Schritt weiter gehen, um zu sehen, wie wir Testcontainers während der Entwicklung mit den neuen Funktionen in Spring Boot 3.1 verwenden können.
Historisch gesehen haben wir unterschiedliche Profile verwendet, um zwischen Entwicklungs- und Testumgebungen zu wechseln. Aber jetzt gibt es eine bessere Möglichkeit, dies zu verwalten und die Entwicklungsumgebung so ähnlich wie möglich der Produktionsumgebung zu gestalten.
Anstatt ein Testprofil zu erstellen, können wir eine neue Hauptmethode erstellen, um unsere Anwendung lokal mit Testcontainers auszuführen:
public class TestMyApplication {
public static void main(String[] args) {
SpringApplication.from(MyApplication::main).run(args);
}
}
Wir müssen diese Klasse innerhalb des Test-Root-Pakets erstellen, um die testcontainers-Abhängigkeiten nur im Testbereich zu halten. Andernfalls werden die Abhängigkeiten in vollständige Jar-Dateien aufgenommen, was die Anwendungsgröße aufbläht, und sie werden in der Produktion nicht verwendet.
Nun müssen wir den Test-Container für PostgreSQL konfigurieren und die neue Main-Methode darüber informieren. Zuerst erstellen wir eine Konfigurationsdatei und annotieren sie mit @TestConfiguration:
@TestConfiguration(proxyBeanMethods = false)
class MyContainersConfiguration {
@Bean
@ServiceConnection
PostgreSQLContainer<?> postgresContainer() {
return new PostgreSQLContainer<>("postgres:16");
}
}
Kurz gesagt, wir erstellen einfach ein Bean, das die Container-Instanz von PostgreSQL repräsentiert. Wir annotieren es erneut mit @ServiceConnection, um Spring Boot mitzuteilen, dass es den Lebenszyklus verwalten sowie die Konfiguration für die Verwendung mit unserer Anwendung übernehmen soll.
Nun aktualisieren wir die test-main
Methode:
public class TestMyApplication {
public static void main(String[] args) {
SpringApplication.from(MyApplication::main)
.with(MyContainersConfiguration.class)
.run(args);
}
}
Endlich können wir die Methode test-main ausführen, um die Anwendung mit Maven zu starten:
./mvnw spring-boot:test-run
Oder mit Gradle:
./gradlew bootTestRun
Dies wird die Anwendung starten und die Container werden automatisch mit der Anwendung hochgefahren und heruntergefahren. Wie wir sehen können, kennen die Maven- und Gradle-Plugins bereits die test-main-Methode und werden die Anwendung damit starten.
5. Speichern der Container-Daten zwischen Neustarts
Mit der obigen Konfiguration erstellen wir jedes Mal, wenn die Anwendung gestartet wird, eine neue Instanz des Containers. Daher verlieren wir alle ihre Daten. Wir möchten jedoch möglicherweise den Zustand des Containers zwischen Neustarts speichern. Um dies zu erreichen, können wir die @RestartScope-Annotation von Spring Boot DevTools auf den Container-Bean-Methoden verwenden:
@Bean
@ServiceConnection
@RestartScope
PostgreSQLContainer<?> postgresContainer() {
return new PostgreSQLContainer<>("postgres:16");
}
Alternativ können wir die experimentelle Funktion der wiederverwendbaren Container in Testcontainers nutzen, indem wir withReuse(true) auf dem Container aufrufen:
new PostgreSQLContainer<>("postgres:16").withReuse(true);
Dies wird die Daten zwischen Neustarts speichern. Außerdem wird die Startzeit der Anwendung beschleunigt, da wir nicht jedes Mal einen neuen Container starten.
6. Fazit
In diesem Artikel haben wir gesehen, wie man Testcontainers in Spring Boot-Integrationstests verwendet. Wir haben gesehen, wie man einen Datenbank-Container mit Testcontainers einrichtet und wie man die @ServiceConnection-Annotation nutzt, um den Prozess zu vereinfachen. Außerdem haben wir uns angesehen, wie man Testcontainers während der Entwicklung mit den neuen Funktionen in Spring Boot 3.1 verwendet.