TestContainers просто потрясающий! Он предоставляет очень удобный способ запуска и CLEANLY разрушения док-контейнеров в тестах JUnit. Эта функция очень полезна для тестирования интеграции приложений с реальными базами данных и любыми другими ресурсами, для которых доступен образ докера.
Моя цель — продемонстрировать пример теста для Spring Boot Application на основе JPA с использованием TestContainers. Образец основан на примере в репозитории github TestContainer .
Образец приложения
Приложение на основе Spring Boot является простым — это приложение на основе Spring Data JPA с веб-слоем, написанным с использованием Spring Web Flux . Весь образец доступен в моем репозитории github, и может быть проще просто следовать коду прямо там.
Сохраняемая сущность City выглядит следующим образом (используя Kotlin ):
01
02
03
04
05
06
07
08
09
10
11
12
13
|
import javax.persistence.Entity import javax.persistence.GeneratedValue import javax.persistence.Id @Entity data class City( @Id @GeneratedValue var id: Long? = null , val name: String, val country: String, val pop: Long ) { constructor() : this (id = null , name = "" , country = "" , pop = 0L) } |
Все, что требуется для обеспечения хранилища для управления этим объектом, — это следующий интерфейс, благодаря отличному проекту Spring Data JPA :
1
2
3
4
|
import org.springframework.data.jpa.repository.JpaRepository import samples.geo.domain.City interface CityRepo: JpaRepository<City, Long> |
Я не буду освещать здесь веб-слой, поскольку он не имеет отношения к обсуждению.
Тестирование репозитория
Spring Boot предоставляет функцию, называемую « Тесты срезов», которая представляет собой удобный способ тестирования различных горизонтальных срезов приложения. Тест для репозитория CityRepo выглядит следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.test.context.junit4.SpringRunner; import samples.geo.domain.City; import samples.geo.repo.CityRepo; import static org.assertj.core.api.Assertions.assertThat; @RunWith (SpringRunner. class ) @DataJpaTest public class CitiesWithEmbeddedDbTest { @Autowired private CityRepo cityRepo; @Test public void testWithDb() { City city1 = cityRepo.save( new City( null , "city1" , "USA" , 20000L)); City city2 = cityRepo.save( new City( null , "city2" , "USA" , 40000L)); assertThat(city1) .matches(c -> c.getId() != null && c.getName() == "city1" && c.getPop() == 20000L); assertThat(city2) .matches(c -> c.getId() != null && c.getName() == "city2" && c.getPop() == 40000L); assertThat(cityRepo.findAll()).containsExactly(city1, city2); } } |
Аннотация «@DataJpaTest» запускает встроенные базы данных h2, настраивает JPA и загружает любые репозитории Spring Data JPA (в данном случае CityRepo).
Этот вид теста работает хорошо, учитывая, что JPA обеспечивает абстракцию базы данных, и если JPA используется правильно, код должен переноситься между любыми поддерживаемыми базами данных. Однако, если предположить, что это приложение будет работать на PostgreSQL в рабочей среде, в идеале должен быть какой-то уровень интеграционного тестирования с базой данных, в который входит TestContainer. Он обеспечивает способ загрузки PostgreSQL в качестве докера контейнер.
TestContainers
Тот же тест хранилища с использованием TestContainers выглядит следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.testcontainers.containers.PostgreSQLContainer; import samples.geo.domain.City; import samples.geo.repo.CityRepo; import java.time.Duration; import static org.assertj.core.api.Assertions.assertThat; @RunWith (SpringRunner. class ) @DataJpaTest @ContextConfiguration (initializers = {CitiesWithPostgresContainerTest.Initializer. class }) public class CitiesWithPostgresContainerTest { @ClassRule public static PostgreSQLContainer postgreSQLContainer = (PostgreSQLContainer) new PostgreSQLContainer( "postgres:10.4" ) .withDatabaseName( "sampledb" ) .withUsername( "sampleuser" ) .withPassword( "samplepwd" ) .withStartupTimeout(Duration.ofSeconds( 600 )); @Autowired private CityRepo cityRepo; @Test public void testWithDb() { City city1 = cityRepo.save( new City( null , "city1" , "USA" , 20000L)); City city2 = cityRepo.save( new City( null , "city2" , "USA" , 40000L)); assertThat(city1) .matches(c -> c.getId() != null && c.getName() == "city1" && c.getPop() == 20000L); assertThat(city2) .matches(c -> c.getId() != null && c.getName() == "city2" && c.getPop() == 40000L); assertThat(cityRepo.findAll()).containsExactly(city1, city2); } 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()); } } } |
Ядро кода выглядит так же, как и в предыдущем тесте, но репозиторий здесь тестируется на реальной базе данных PostgreSQL. Чтобы вдаваться в подробности —
Контейнер PostgreSQL запускается с использованием правила класса JUnit, которое запускается до запуска любого из тестов. Эта зависимость извлекается с использованием зависимости gradle следующего типа:
1
|
testCompile( "org.testcontainers:postgresql:1.7.3" ) |
Правило класса запускает Docker-контейнер PostgreSQL (postgres: 10.4) и настраивает базу данных и учетные данные для базы данных. Теперь, с точки зрения Spring Boot, эти детали необходимо передать приложению, поскольку свойства ДО того, как Spring начинает создавать контекст теста для запуска теста, и это делается для теста с использованием ApplicationContextInitializer , который вызывается Spring очень рано жизненный цикл Spring Context.
Пользовательский ApplicationContextInitializer, который устанавливает имя базы данных, URL и учетные данные пользователя, подключается к тесту с помощью этого кода:
01
02
03
04
05
06
07
08
09
10
|
... import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; ... @RunWith (SpringRunner. class ) @DataJpaTest @ContextConfiguration (initializers = {CitiesWithPostgresContainerTest.Initializer. class }) public class CitiesWithPostgresContainerTest { ... |
При установленной плите котла TestContainer и тестирование среза пружинной загрузки возьмет на себя выполнение теста. Что еще более важно TestContainers также заботится о демонтаже, правило класса JUnit гарантирует, что после завершения теста контейнеры останавливаются и удаляются.
Вывод
Это был вихревой тур по TestContainers, TestContainers — это гораздо больше, чем то, что я здесь рассмотрел, но я надеюсь, что это даст представление о том, что возможно с помощью этой превосходной библиотеки и как ее настроить с помощью Spring Boot. Этот образец доступен в моем репозитории github
Смотрите оригинальную статью здесь: TestContainers и Spring Boot
Мнения, высказанные участниками Java Code Geeks, являются их собственными. |