日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

在Spring Boot测试中使用Testcontainer进行数据库集成测试

發布時間:2023/12/3 javascript 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 在Spring Boot测试中使用Testcontainer进行数据库集成测试 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在此博客文章中,我想演示如何在Spring Boot測試中集成Testcontainer以便與數據庫一起運行集成測試。 我沒有使用Testcontainers的Spring Boot模塊。 如何與他們合作,我將在另一篇博客文章中進行介紹。 所有示例都可以在GitHub上找到 。

為什么要使用測試容器?

Testcontainers是一個庫,可幫助在基于Docker容器的集成測試中集成數據庫等基礎架構組件。 它有助于避免編寫集成測試。 這些是根據另一個系統的正確性通過或失敗的測試。 使用Testcontainer,我可以控制這些從屬系統。

域介紹

進一步的示例展示了不同的方法,該方法如何通過數據庫中不同的存儲庫實現來保存一些英雄對象,以及相應的測試如何。

package com.github.sparsick.testcontainerspringboot.hero.universum;import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import java.util.Objects;public class Hero {private Long id;private String name;private String city;private ComicUniversum universum;public Hero(String name, String city, ComicUniversum universum) {this.name = name;this.city = city;this.universum = universum;}public String getName() {return name;}public String getCity() {return city;}public ComicUniversum getUniversum() {return universum;} }

所有其他存儲庫都是Spring Boot Web應用程序的一部分。 因此,在本博客文章的結尾,我將演示如何為整個Web應用程序(包括數據庫)編寫測試。 讓我們從一個簡單的示例開始,該示例基于JDBC。

基于JDBC測試存儲庫

假設我們有以下基于JDBC的存儲庫實現。 我們有兩種方法,一種是將英雄添加到數據庫中,另一種是從數據庫中獲取所有英雄。

package com.github.sparsick.testcontainerspringboot.hero.universum;import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository;import javax.sql.DataSource; import java.util.Collection;@Repository public class HeroClassicJDBCRepository {private final JdbcTemplate jdbcTemplate;public HeroClassicJDBCRepository(DataSource dataSource) {jdbcTemplate = new JdbcTemplate(dataSource);}public void addHero(Hero hero) {jdbcTemplate.update("insert into hero (city, name, universum) values (?,?,?)",hero.getCity(), hero.getName(), hero.getUniversum().name());}public CollectionallHeros() {return jdbcTemplate.query("select * From hero",(resultSet, i) -> new Hero(resultSet.getString("name"),resultSet.getString("city"),ComicUniversum.valueOf(resultSet.getString("universum"))));}}

對于此存儲庫,我們可以編寫普通的JUnit5測試,而無需加載Spring應用程序上下文。 因此,首先,我們必須建立對測試庫的依賴關系,在這種情況下為JUnit5和Testcontainers。 作為構建工具,我使用Maven。 這兩個測試庫都提供了所謂的BOM“物料清單” ,這有助于避免我所使用的依賴項中的版本不匹配。 作為數據庫,我想使用MySQL。 因此,除了核心模塊testcontainers之外,我還使用了Testcontainers的模塊mysql 。 它提供了一個預定義的MySQL容器。 為了簡化JUnit5測試代碼中專門的容器設置,Testcontainers提供了一個JUnit5模塊junit-jupiter 。

<dependencies><dependency><groupId>org.testcontainers</groupId><artifactId>testcontainers</artifactId><scope>test</scope></dependency><dependency><groupId>org.testcontainers</groupId><artifactId>junit-jupiter</artifactId><scope>test</scope></dependency><dependency><groupId>org.testcontainers</groupId><artifactId>mysql</artifactId><scope>test</scope></dependency><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><scope>test</scope></dependency> </dependencies> <dependencyManagement><dependencies><dependency><groupId>org.junit</groupId><artifactId>junit-bom</artifactId><version>${junit.jupiter.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.testcontainers</groupId><artifactId>testcontainers-bom</artifactId><version>${testcontainers.version}</version><type>pom</type><scope>import</scope></dependency></dependencies> </dependencyManagement>

現在,我們擁有編寫第一個測試的所有內容。

package com.github.sparsick.testcontainerspringboot.hero.universum;import ...@Testcontainers class HeroClassicJDBCRepositoryIT {@Containerprivate MySQLContainer database = new MySQLContainer();private HeroClassicJDBCRepository repositoryUnderTest;@Testvoid testInteractionWithDatabase() {ScriptUtils.runInitScript(new JdbcDatabaseDelegate(database, ""),"ddl.sql");repositoryUnderTest = new HeroClassicJDBCRepository(dataSource());repositoryUnderTest.addHero(new Hero("Batman", "Gotham City", ComicUniversum.DC_COMICS));Collection<Hero> heroes = repositoryUnderTest.allHeros();assertThat(heroes).hasSize(1);}@NotNullprivate DataSource dataSource() {MysqlDataSource dataSource = new MysqlDataSource();dataSource.setUrl(database.getJdbcUrl());dataSource.setUser(database.getUsername());dataSource.setPassword(database.getPassword());return dataSource;} }

讓我們看看如何為測試準備數據庫。 首先,我們使用@Testcontainers注釋測試類。 該注釋的后面隱藏了Testcontainers提供的JUnit5擴展。 它檢查Docker是否安裝在計算機上,并在測試期間啟動和停止容器。 但是,Testcontainers如何知道應該從哪個容器開始? 在這里,注釋@Container幫助。 它標記了應由Testcontainers擴展管理的容器。 在這種情況下,一個MySQLContainer由Testcontainers模塊提供mysql 。 此類提供了MySQL Docker容器,并處理諸如設置數據庫用戶,識別何時可以使用數據庫等問題。一旦數據庫準備就緒可以使用,就必須設置數據庫架構。 測試容器也可以在此處提供支持。 ScriptUtils. runInitScript (new JdbcDatabaseDelegate(database, ""),"ddl.sql"); 確保按照SQL腳本ddl.sql定義的那樣設置架構。

-- ddl.sql create table hero (id bigint AUTO_INCREMENT PRIMARY KEY, city varchar(255), name varchar(255), universum varchar(255)) engine=InnoDB

現在,我們準備建立受測試的存儲庫。 因此,我們需要DataSource對象的數據庫連接信息。 在底層,Testcontainers會搜索可用的端口,并將容器綁定到該空閑端口上。 在每個通過Testcontainer啟動的容器上,此端口號均不同。 此外,它使用用戶名和密碼在容器中配置數據庫。 因此,我們必須詢問MySQLContainer對象數據庫憑據和JDBC URL的狀態。 有了這些信息,我們就可以建立被測試的存儲庫( repositoryUnderTest = new HeroClassicJDBCRepository(dataSource()); )并完成測試。

如果運行測試,則會收到以下錯誤消息:

17:18:50.990 [ducttape-1] DEBUG com.github.dockerjava.core.command.AbstrDockerCmd - Cmd: org.testcontainers.dockerclient.transport.okhttp.OkHttpDockerCmdExecFactory$1@1adc57a8 17:18:51.492 [ducttape-1] DEBUG org.testcontainers.dockerclient.DockerClientProviderStrategy - Pinging docker daemon... 17:18:51.493 [ducttape-1] DEBUG com.github.dockerjava.core.command.AbstrDockerCmd - Cmd: org.testcontainers.dockerclient.transport.okhttp.OkHttpDockerCmdExecFactory$1@3e5b3a3b 17:18:51.838 [main] DEBUG org.testcontainers.dockerclient.DockerClientProviderStrategy - UnixSocketClientProviderStrategy: failed with exception InvalidConfigurationException (ping failed). Root cause LastErrorException ([111] Verbindungsaufbau abgelehnt) 17:18:51.851 [main] DEBUG org.rnorth.tcpunixsocketproxy.ProxyPump - Listening on localhost/127.0.0.1:41039 and proxying to /var/run/docker.sock 17:18:51.996 [ducttape-0] DEBUG org.testcontainers.dockerclient.DockerClientProviderStrategy - Pinging docker daemon... 17:18:51.997 [ducttape-1] DEBUG org.testcontainers.dockerclient.DockerClientProviderStrategy - Pinging docker daemon... 17:18:51.997 [ducttape-0] DEBUG com.github.dockerjava.core.command.AbstrDockerCmd - Cmd: org.testcontainers.dockerclient.transport.okhttp.OkHttpDockerCmdExecFactory$1@5d43d23e 17:18:51.997 [ducttape-1] DEBUG com.github.dockerjava.core.command.AbstrDockerCmd - Cmd: org.testcontainers.dockerclient.transport.okhttp.OkHttpDockerCmdExecFactory$1@7abf08d2 17:18:52.002 [tcp-unix-proxy-accept-thread] DEBUG org.rnorth.tcpunixsocketproxy.ProxyPump - Accepting incoming connection from /127.0.0.1:41998 17:19:01.866 [main] DEBUG org.testcontainers.dockerclient.DockerClientProviderStrategy - ProxiedUnixSocketClientProviderStrategy: failed with exception InvalidConfigurationException (ping failed). Root cause TimeoutException (null) 17:19:01.870 [main] ERROR org.testcontainers.dockerclient.DockerClientProviderStrategy - Could not find a valid Docker environment. Please check configuration. Attempted configurations were: 17:19:01.872 [main] ERROR org.testcontainers.dockerclient.DockerClientProviderStrategy - EnvironmentAndSystemPropertyClientProviderStrategy: failed with exception InvalidConfigurationException (ping failed) 17:19:01.873 [main] ERROR org.testcontainers.dockerclient.DockerClientProviderStrategy - EnvironmentAndSystemPropertyClientProviderStrategy: failed with exception InvalidConfigurationException (ping failed) 17:19:01.874 [main] ERROR org.testcontainers.dockerclient.DockerClientProviderStrategy - UnixSocketClientProviderStrategy: failed with exception InvalidConfigurationException (ping failed). Root cause LastErrorException ([111] Verbindungsaufbau abgelehnt) 17:19:01.875 [main] ERROR org.testcontainers.dockerclient.DockerClientProviderStrategy - ProxiedUnixSocketClientProviderStrategy: failed with exception InvalidConfigurationException (ping failed). Root cause TimeoutException (null) 17:19:01.875 [main] ERROR org.testcontainers.dockerclient.DockerClientProviderStrategy - As no valid configuration was found, execution cannot continue 17:19:01.900 [main] DEBUG [mysql:5.7.22] - mysql:5.7.22 is not in image name cache, updating... Mai 01, 2020 5:19:01 NACHM. org.junit.jupiter.engine.execution.JupiterEngineExecutionContext close SEVERE: Caught exception while closing extension context: org.junit.jupiter.engine.descriptor.MethodExtensionContext@2e6a5539 org.testcontainers.containers.ContainerLaunchException: Container startup failedat org.testcontainers.containers.GenericContainer.doStart(GenericContainer.java:322)at org.testcontainers.containers.GenericContainer.start(GenericContainer.java:302)at org.testcontainers.junit.jupiter.TestcontainersExtension$StoreAdapter.start(TestcontainersExtension.java:173)at org.testcontainers.junit.jupiter.TestcontainersExtension$StoreAdapter.access$100(TestcontainersExtension.java:160)at org.testcontainers.junit.jupiter.TestcontainersExtension.lambda$null$3(TestcontainersExtension.java:50)at org.junit.jupiter.engine.execution.ExtensionValuesStore.lambda$getOrComputeIfAbsent$0(ExtensionValuesStore.java:81)at org.junit.jupiter.engine.execution.ExtensionValuesStore$MemoizingSupplier.get(ExtensionValuesStore.java:182)at org.junit.jupiter.engine.execution.ExtensionValuesStore.closeAllStoredCloseableValues(ExtensionValuesStore.java:58)at org.junit.jupiter.engine.descriptor.AbstractExtensionContext.close(AbstractExtensionContext.java:73)at org.junit.jupiter.engine.execution.JupiterEngineExecutionContext.close(JupiterEngineExecutionContext.java:53)at org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.cleanUp(JupiterTestDescriptor.java:222)at org.junit.jupiter.engine.descriptor.JupiterTestDescriptor.cleanUp(JupiterTestDescriptor.java:57)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$cleanUp$9(NodeTestTask.java:151)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.cleanUp(NodeTestTask.java:151)at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:83)at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58) Caused by: org.testcontainers.containers.ContainerFetchException: Can't get Docker image: RemoteDockerImage(imageNameFuture=java.util.concurrent.CompletableFuture@539d019[Completed normally], imagePullPolicy=DefaultPullPolicy(), dockerClient=LazyDockerClient.INSTANCE)at org.testcontainers.containers.GenericContainer.getDockerImageName(GenericContainer.java:1265)at org.testcontainers.containers.GenericContainer.logger(GenericContainer.java:600)at org.testcontainers.containers.GenericContainer.doStart(GenericContainer.java:311)... 47 more Caused by: java.lang.IllegalStateException: Previous attempts to find a Docker environment failed. Will not retry. Please see logs and check configurationat org.testcontainers.dockerclient.DockerClientProviderStrategy.getFirstValidStrategy(DockerClientProviderStrategy.java:78)at org.testcontainers.DockerClientFactory.client(DockerClientFactory.java:115)at org.testcontainers.LazyDockerClient.getDockerClient(LazyDockerClient.java:14)at org.testcontainers.LazyDockerClient.inspectImageCmd(LazyDockerClient.java:12)at org.testcontainers.images.LocalImagesCache.refreshCache(LocalImagesCache.java:42)at org.testcontainers.images.AbstractImagePullPolicy.shouldPull(AbstractImagePullPolicy.java:24)at org.testcontainers.images.RemoteDockerImage.resolve(RemoteDockerImage.java:62)at org.testcontainers.images.RemoteDockerImage.resolve(RemoteDockerImage.java:25)at org.testcontainers.utility.LazyFuture.getResolvedValue(LazyFuture.java:20)at org.testcontainers.utility.LazyFuture.get(LazyFuture.java:27)at org.testcontainers.containers.GenericContainer.getDockerImageName(GenericContainer.java:1263)... 49 moreorg.testcontainers.containers.ContainerLaunchException: Container startup failed

該錯誤消息表示Docker守護程序未運行。 確保Docker守護程序正在運行后,測試運行將成功。

控制臺輸出中有很多調試消息。 測試中的日志記錄輸出可以通過src/test/resources的logback.xml文件進行配置:

<?xml version="1.0" encoding="UTF-8" ?> <configuration><include resource="org/springframework/boot/logging/logback/base.xml"/><root level="info"><appender-ref ref="CONSOLE" /></root> </configuration>

有關日志記錄的Spring Boot文檔建議使用logback-spring.xml作為配置文件。 但是普通的JUnit5測試無法識別它,只有@SpringBootTest注釋了測試。 兩種測試都使用logback.xml 。

基于JPA實體管理器測試存儲庫

現在,我們要使用經典的實體管理器來實現基于JPA的存儲庫。 假設,我們通過三種方法執行以下操作:將英雄添加到數據庫中,通過搜索條件查找英雄,并從數據庫中獲取所有英雄。 實體管理器由Spring的應用程序上下文配置( @PersistenceContext負責)。

package com.github.sparsick.testcontainerspringboot.hero.universum;import ...@Repository public class HeroClassicJpaRepository {@PersistenceContextprivate EntityManager em;@Transactionalpublic void addHero(Hero hero) {em.persist(hero);}public CollectionallHeros() {return em.createQuery("Select hero FROM Hero hero", Hero.class).getResultList();}public CollectionfindHerosBySearchCriteria(String searchCriteria) {return em.createQuery("SELECT hero FROM Hero hero " +"where hero.city LIKE :searchCriteria OR " +"hero.name LIKE :searchCriteria OR " +"hero.universum = :searchCriteria",Hero.class).setParameter("searchCriteria", searchCriteria).getResultList();}}

作為JPA的實現,我們選擇Hibernate和MySQL作為數據庫提供程序。 我們必須配置休眠應使用的方言。

# src/main/resources/application.properties spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect

在application.properties您還配置數據庫連接等。

為了在測試中正確設置實體管理器,我們必須在應用程序上下文中運行測試,以便Spring可以正確配置實體管理器。

Spring Boot帶來了一些測試支持類。 因此,我們必須向該項目添加進一步的測試依賴項。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope> </dependency>

該入門程序還包括JUnit Jupiter依賴關系和其他測試庫的依賴關系,因此您可以根據需要從依賴關系聲明中刪除這些依賴關系。

現在,我們擁有編寫測試的所有內容。

package com.github.sparsick.testcontainerspringboot.hero.universum;import ...@SpringBootTest @Testcontainers @ContextConfiguration(initializers = HeroClassicJpaRepositoryTest.Initializer.class) class HeroClassicJpaRepositoryIT {@Containerprivate static MySQLContainer database = new MySQLContainer();@Autowiredprivate HeroClassicJpaRepository repositoryUnderTest;@Testvoid findHeroByCriteria(){repositoryUnderTest.addHero(new Hero("Batman", "Gotham City", ComicUniversum.DC_COMICS));Collectionheros = repositoryUnderTest.findHerosBySearchCriteria("Batman");assertThat(heros).contains(new Hero("Batman", "Gotham City", ComicUniversum.DC_COMICS));}static class Initializer implementsApplicationContextInitializer{public void initialize(ConfigurableApplicationContextconfigurableApplicationContext) {TestPropertyValues.of("spring.datasource.url=" + database.getJdbcUrl(),"spring.datasource.username=" + database.getUsername(),"spring.datasource.password=" + database.getPassword()).applyTo(configurableApplicationContext.getEnvironment());}} }

測試類帶有一些注釋。 第一個是SpringBootTest從而在測試期間啟動Spring應用程序上下文。 下一個是@Testcontainers 。 從上次測試中我們已經知道了該注釋。 它是一個JUnit5擴展,用于管理測試期間啟動和停止docker容器。 最后一個是@ContextConfiguration(initializers = HeroClassicJpaRepositoryTest.Initializer.class)因此我們可以以編程方式配置應用程序上下文。 在我們的例子中,我們想用從Testcontainers管理的數據庫容器對象獲得的數據庫信息覆蓋數據庫連接配置。 就像我們在上面的JDBC測試中看到的那樣,我們注釋數據庫容器private static MySQLContainer database = new MySQLContainer(); 與@Container 。 它表明此容器應由Testcontainers管理。 這與上面的JDBC設置略有不同。 在這里, MySQLContainer database是static ,在JDBC設置中它是一個普通的類字段。 這里,它必須是靜態的,因為容器必須在應用程序上下文啟動之前啟動,以便我們進行更改以將數據庫連接配置傳遞給應用程序上下文。 為此, static class Initializer負責。 在啟動階段,它將覆蓋應用程序上下文配置。 最后一步是在數據庫中設置數據庫架構。 JPA在這里可以提供幫助。 它可以自動創建數據庫架構。 您必須使用

# src/test/resources/application.properties spring.jpa.hibernate.ddl-auto=update

或者,您可以在static class Initializer添加此屬性。

現在,我們可以將存儲庫注入測試( @Autowired private HeroClassicJpaRepository repositoryUnderTest )。 該存儲庫由Spring配置并可以進行測試。

基于Spring Data JPA測試存儲庫

如今,在Spring Boot應用程序中通常將JPA與Spring Data結合使用,因此我們重寫存儲庫以使用Spring Data JPA代替純JPA。 結果是擴展了Spring Data的CrudRepository的接口,因此我們具有所有基本操作,如保存,刪除,通過id更新查找等。 對于按條件搜索功能,我們必須使用@Query注釋定義一個具有JPA查詢的方法。

package com.github.sparsick.testcontainerspringboot.hero.universum;import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param;import java.util.List;public interface HeroSpringDataJpaRepository extends CrudRepository<Hero, Long> {@Query("SELECT hero FROM Hero hero where hero.city LIKE :searchCriteria OR hero.name LIKE :searchCriteria OR hero.universum = :searchCriteria")List<Hero> findHerosBySearchCriteria(@Param("searchCriteria") String searchCriteria); }

正如上面在經典JPA示例中所提到的,在這里也是如此,我們必須配置Hibernate選擇的JPA實現應使用哪種SQL方言,以及如何設置數據庫模式。

與測試配置相同,同樣,我們需要一個帶有Spring應用程序上下文的測試,以便為測試正確配置存儲庫。 但是這里我們不需要使用@SpringBootTest來啟動整個應用程序上下文。 相反,我們使用@DataJpaTest 。 該批注僅使用持久層所需的bean啟動應用程序上下文。

package com.github.sparsick.testcontainerspringboot.hero.universum;import ...@DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @ContextConfiguration(initializers = HeroSpringDataJpaRepositoryIT.Initializer.class) @Testcontainers class HeroSpringDataJpaRepositoryIT {@Containerprivate static MySQLContainer database = new MySQLContainer();@Autowiredprivate HeroSpringDataJpaRepository repositoryUnderTest;@Testvoid findHerosBySearchCriteria() {repositoryUnderTest.save(new Hero("Batman", "Gotham City", ComicUniversum.DC_COMICS));Collection<Hero> heros = repositoryUnderTest.findHerosBySearchCriteria("Batman");assertThat(heros).hasSize(1).contains(new Hero("Batman", "Gotham City", ComicUniversum.DC_COMICS));}static class Initializer implementsApplicationContextInitializer<ConfigurableApplicationContext> {public void initialize(ConfigurableApplicationContextconfigurableApplicationContext) {TestPropertyValues.of("spring.datasource.url=" + database.getJdbcUrl(),"spring.datasource.username=" + database.getUsername(),"spring.datasource.password=" + database.getPassword()).applyTo(configurableApplicationContext.getEnvironment());}} }

@DataJpaTest作為默認啟動內存數據庫。 但是我們希望使用由Testcontainers提供的容器化數據庫。 因此,我們必須添加注釋@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 。 這將禁用啟動內存數據庫。 其余測試配置與上述針對純JPA示例的測試中的配置相同。

測試存儲庫但重用數據庫

隨著測試數量的增加,每次測試花費相當長的時間變得越來越重要,因為每次啟動和初始化新數據庫時,這種測試就變得越來越重要。 一種想法是在每次測試中重用數據庫。 在這里, 單一容器模式可以提供幫助。 在所有測試開始運行之前,將啟動并初始化一次數據庫。 為此,每個需要數據庫的測試都必須擴展一個抽象類,該類負責在所有測試運行之前啟動和初始化數據庫。

package com.github.sparsick.testcontainerspringboot.hero.universum;import ...@ContextConfiguration(initializers = DatabaseBaseTest.Initializer.class) public abstract class DatabaseBaseTest {static final MySQLContainer DATABASE = new MySQLContainer();static {DATABASE.start();}static class Initializer implementsApplicationContextInitializer{public void initialize(ConfigurableApplicationContextconfigurableApplicationContext) {TestPropertyValues.of("spring.datasource.url=" + DATABASE.getJdbcUrl(),"spring.datasource.username=" + DATABASE.getUsername(),"spring.datasource.password=" + DATABASE.getPassword()).applyTo(configurableApplicationContext.getEnvironment());}} }

在這個抽象類中,我們為擴展該抽象類和該數據庫的應用程序上下文的所有測試配置一次啟動的數據庫。 請注意,這里我們不使用Testcontainers的注釋,因為此注釋會確保容器在每次測試后啟動和停止。 但這我們會避免。 因此,我們自己啟動數據庫。 對于停止數據庫,我們不需要注意。 為此,Testcontainers的側車集裝箱ryuk會非常小心。

現在,每個需要數據庫的測試類都擴展了這個抽象類。 我們必須配置的唯一一件事就是應如何初始化應用程序上下文。 這意味著,當您需要整個應用程序上下文時,請使用@SpringBootTest 。 如果只需要持久層,則將@DataJpaTest與@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 。

@DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class HeroSpringDataJpaRepositoryReuseDatabaseIT extends DatabaseBaseTest {@Autowiredprivate HeroSpringDataJpaRepository repositoryUnderTest;@Testvoid findHerosBySearchCriteria() {repositoryUnderTest.save(new Hero("Batman", "Gotham City", ComicUniversum.DC_COMICS));Collection<Hero> heros = repositoryUnderTest.findHerosBySearchCriteria("Batman");assertThat(heros).contains(new Hero("Batman", "Gotham City", ComicUniversum.DC_COMICS));} }

測試包括數據庫在內的整個Web應用程序

現在我們要測試整個應用程序,從控制器到數據庫。 控制器實現如下所示:

@RestController public class HeroRestController {private final HeroSpringDataJpaRepository heroRepository;public HeroRestController(HeroSpringDataJpaRepository heroRepository) {this.heroRepository = heroRepository;}@GetMapping("heros")public Iterable<Hero> allHeros(String searchCriteria) {if (searchCriteria == null || searchCriteria.equals("")) {return heroRepository.findAll();}return heroRepository.findHerosBySearchCriteria(searchCriteria);}@PostMapping("hero")public void hero(@RequestBody Hero hero) {heroRepository.save(hero);} }

測試從數據庫到控制器的整個過程的測試類看起來像這樣

SpringBootTest @ContextConfiguration(initializers = HeroRestControllerIT.Initializer.class) @AutoConfigureMockMvc @Testcontainers class HeroRestControllerIT {@Containerprivate static MySQLContainer database = new MySQLContainer();@Autowiredprivate MockMvc mockMvc;@Autowiredprivate HeroSpringDataJpaRepository heroRepository;@Testvoid allHeros() throws Exception {heroRepository.save(new Hero("Batman", "Gotham City", ComicUniversum.DC_COMICS));heroRepository.save(new Hero("Superman", "Metropolis", ComicUniversum.DC_COMICS));mockMvc.perform(get("/heros")).andExpect(status().isOk()).andExpect(jsonPath("$[*].name", containsInAnyOrder("Batman", "Superman")));}static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {@Overridepublic void initialize(ConfigurableApplicationContext configurableApplicationContext) {TestPropertyValues.of("spring.datasource.url=" + database.getJdbcUrl(),"spring.datasource.username=" + database.getUsername(),"spring.datasource.password=" + database.getPassword()).applyTo(configurableApplicationContext.getEnvironment());}} }

上一節中的測試為數據庫和應用程序設置了測試。 一件事是不同的。 我們通過@AutoConfigureMockMvc添加了MockMVC支持。 這有助于通過HTTP層編寫測試。

當然,您也可以使用擴展了抽象類DatabaseBaseTest的單個容器模式。

結論與概述

這篇博客文章展示了我們如何使用Testcontainers在Spring Boot中編寫一些持久層實現的測試。 我們還將看到如何在多個測試中重用數據庫實例,以及如何從控制器tor數據庫為整個Web應用程序編寫測試。 所有代碼段都可以在GitHub上找到 。 在另一篇博客文章中,我將展示如何使用Testcontainers Spring Boot模塊編寫測試。

您還有其他針對持久層編寫測試的想法嗎? 請讓我知道并寫評論。

更多的信息

  • BOM“物料清單”的概念
  • 測試容器
  • Spring Boot文檔–日志記錄
  • Spring Boot文檔–自動配置的數據JPA測試
  • 測試容器–單容器模式
  • Spring Boot文檔– MockMVC
  • GitHub存儲庫中的完整示例
  • 翻譯自: https://www.javacodegeeks.com/2020/05/using-testcontainers-in-spring-boot-tests-for-database-integration-tests.html

    總結

    以上是生活随笔為你收集整理的在Spring Boot测试中使用Testcontainer进行数据库集成测试的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。