Datenbankintegrationstests mit Spring Boot und Testcontainern

1. Übersicht


Mit Spring Data JPA können Sie auf einfache Weise Datenbankabfragen erstellen und diese mithilfe der integrierten H2-Datenbank testen.


Manchmal ist das Testen einer realen Datenbank jedoch viel nützlicher , insbesondere wenn Abfragen verwendet werden, die an eine bestimmte Datenbankimplementierung gebunden sind.


In diesem Handbuch wird gezeigt, wie Testcontainer für Integrationstests mit Spring Data JPA- und PostgreSQL-Datenbanken verwendet werden.


Im vorherigen Artikel haben wir mehrere Datenbankabfragen erstellt, hauptsächlich unter Verwendung der Annotation @Query , die wir jetzt testen.


2. Konfiguration


Um die PostgreSQL-Datenbank in unseren Tests zu verwenden, müssen wir die Testcontainer-Abhängigkeit nur von Tests und den PostgreSQL-Treiber zu unserer Datei pom.xml hinzufügen:


<dependency> <groupId>org.testcontainers</groupId> <artifactId>postgresql</artifactId> <version>1.10.6</version> <scope>test</scope> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.2.5</version> </dependency> 

Wir werden auch die Datei application.properties im Verzeichnis der Testressourcen erstellen, in der wir Spring so einstellen, dass die erforderliche Treiberklasse verwendet wird, und das Datenbankschema bei jeder Testausführung erstellen und löschen:


 spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=create-drop 

3. Unit Test


Um eine PostgreSQL-Instanz in einer einzelnen Testklasse zu verwenden, müssen Sie eine Containerdefinition erstellen und dann ihre Parameter verwenden, um die Verbindung herzustellen:


 @RunWith(SpringRunner.class) @SpringBootTest @ContextConfiguration(initializers = {UserRepositoryTCIntegrationTest.Initializer.class}) public class UserRepositoryTCIntegrationTest extends UserRepositoryCommonIntegrationTests { @ClassRule public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer("postgres:11.1") .withDatabaseName("integration-tests-db") .withUsername("sa") .withPassword("sa"); static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { public void initialize(ConfigurableApplicationContext configurableApplicationContext) { TestPropertyValues.of( "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl(), "spring.datasource.username=" + postgreSQLContainer.getUsername(), "spring.datasource.password=" + postgreSQLContainer.getPassword() ).applyTo(configurableApplicationContext.getEnvironment()); } } } 

Im obigen Beispiel haben wir @ClassRule von JUnit verwendet, um den Datenbankcontainer zu konfigurieren, bevor die Testmethoden ausgeführt wurden . Wir haben auch eine statische innere Klasse erstellt, die ApplicationContextInitializer implementiert. Schließlich haben wir die Annotation @ContextConfiguration auf unsere @ContextConfiguration mit der Initialisierungsklasse als Parameter angewendet.


Nachdem Sie diese drei Schritte ausgeführt haben, können Sie die Verbindungsparameter festlegen, bevor Sie den Spring-Kontext veröffentlichen.


Jetzt verwenden wir zwei UPDATE-Abfragen aus dem vorherigen Artikel:


 @Modifying @Query("update User u set u.status = :status where u.name = :name") int updateUserSetStatusForName(@Param("status") Integer status, @Param("name") String name); @Modifying @Query(value = "UPDATE Users u SET u.status = ? WHERE u.name = ?", nativeQuery = true) int updateUserSetStatusForNameNative(Integer status, String name); 

Und testen Sie in einer optimierten Laufzeitumgebung:


 @Test @Transactional public void givenUsersInDB_WhenUpdateStatusForNameModifyingQueryAnnotationJPQL_ThenModifyMatchingUsers(){ insertUsers(); int updatedUsersSize = userRepository.updateUserSetStatusForName(0, "SAMPLE"); assertThat(updatedUsersSize).isEqualTo(2); } @Test @Transactional public void givenUsersInDB_WhenUpdateStatusForNameModifyingQueryAnnotationNative_ThenModifyMatchingUsers(){ insertUsers(); int updatedUsersSize = userRepository.updateUserSetStatusForNameNative(0, "SAMPLE"); assertThat(updatedUsersSize).isEqualTo(2); } private void insertUsers() { userRepository.save(new User("SAMPLE", "email@example.com", 1)); userRepository.save(new User("SAMPLE1", "email2@example.com", 1)); userRepository.save(new User("SAMPLE", "email3@example.com", 1)); userRepository.save(new User("SAMPLE3", "email4@example.com", 1)); userRepository.flush(); } 

Im obigen Szenario ist der erste Test erfolgreich und der zweite löst eine InvalidDataAccessResourceUsageException mit der folgenden Meldung aus:


 Caused by: org.postgresql.util.PSQLException: ERROR: column "u" of relation "users" does not exist 

Wenn wir dieselben Tests mit der integrierten H2-Datenbank ausführen würden, wären beide erfolgreich, aber PostgreSQL akzeptiert keine Aliase in der SET-Anweisung. Wir können die Anfrage schnell beheben, indem wir den problematischen Alias ​​entfernen:


 @Modifying @Query(value = "UPDATE Users u SET status = ? WHERE u.name = ?", nativeQuery = true) int updateUserSetStatusForNameNative(Integer status, String name); 

Diesmal haben beide Tests erfolgreich bestanden. In diesem Beispiel haben wir Testcontainer verwendet, um ein Problem mit einer nativen Abfrage zu identifizieren, das andernfalls erst nach dem Aufrufen der Produktionsdatenbank erkannt würde . Es sollte auch beachtet werden, dass die Verwendung von JPQL- Abfragen im Allgemeinen sicherer ist, da Spring sie je nach verwendetem Datenbankanbieter korrekt übersetzt.


4. Freigegebene Datenbankinstanz


Im vorherigen Abschnitt haben wir beschrieben, wie Testcontainer in einem einzelnen Test verwendet werden. In realen Fällen möchte ich aufgrund der relativ langen Startzeit denselben Datenbankcontainer in mehreren Tests verwenden.


Erstellen wir eine gemeinsame Klasse zum Erstellen eines Datenbankcontainers, indem wir PostgreSQLContainer erben und die Methoden start () und stop () überschreiben:


 public class BaeldungPostgresqlContainer extends PostgreSQLContainer<BaeldungPostgresqlContainer> { private static final String IMAGE_VERSION = "postgres:11.1"; private static BaeldungPostgresqlContainer container; private BaeldungPostgresqlContainer() { super(IMAGE_VERSION); } public static BaeldungPostgresqlContainer getInstance() { if (container == null) { container = new BaeldungPostgresqlContainer(); } return container; } @Override public void start() { super.start(); System.setProperty("DB_URL", container.getJdbcUrl()); System.setProperty("DB_USERNAME", container.getUsername()); System.setProperty("DB_PASSWORD", container.getPassword()); } @Override public void stop() { //do nothing, JVM handles shut down } } 

Wenn Sie die stop () -Methode leer lassen, kann die JVM die Fertigstellung des Containers selbst durchführen. Wir implementieren auch einen einfachen Singleton, bei dem nur der erste Test den Container startet und jeder nachfolgende Test eine vorhandene Instanz verwendet. In der start () -Methode verwenden wir System # setProperty , um die Verbindungsparameter in Umgebungsvariablen zu speichern.


Jetzt können wir sie in die Datei application.properties schreiben:


 spring.datasource.url=${DB_URL} spring.datasource.username=${DB_USERNAME} spring.datasource.password=${DB_PASSWORD} 

Jetzt verwenden wir unsere Utility-Klasse in der Testdefinition:


 @RunWith(SpringRunner.class) @SpringBootTest public class UserRepositoryTCAutoIntegrationTest { @ClassRule public static PostgreSQLContainer postgreSQLContainer = BaeldungPostgresqlContainer.getInstance(); // tests } 

Wie in den vorherigen Beispielen haben wir die Annotation @ClassRule auf das Feld mit der Containerdefinition angewendet. Daher werden die DataSource-Verbindungsparameter vor dem Erstellen des Spring-Kontexts mit den richtigen Werten gefüllt.


Jetzt können wir mehrere Tests mit derselben Datenbankinstanz implementieren, indem wir einfach das Feld mit der Annotation @ClassRule festlegen, die mit unserer Dienstprogrammklasse BaeldungPostgresqlContainer erstellt wurde .


5. Fazit


In diesem Artikel haben wir Testmethoden in einer Produktionsdatenbank unter Verwendung von Testcontainern gezeigt.


Wir haben auch Beispiele für die Verwendung eines einzelnen Tests mit dem ApplicationContextInitializer- Mechanismus von Spring sowie für die Implementierung einer Klasse zur Wiederverwendung einer Datenbankinstanz untersucht.


Wir haben auch gezeigt, wie Testcontainer helfen können, Kompatibilitätsprobleme zwischen mehreren Datenbankanbietern zu identifizieren, insbesondere bei nativen Abfragen.


Wie immer ist der vollständige Code in diesem Artikel auf GitHub verfügbar.

Source: https://habr.com/ru/post/de454970/


All Articles