1 Ağustos 2021 Pazar

SpringTest Testcontainers PostgreSQLContainer

Giriş
Not : TestContainer yerine zonky kullanılabilir.

Test @TestContainers anotasyonu mecburen eklenir

Maven
Örnek
Şu satırı dahil ederiz
<properties>
  <testcontainers.version>1.16.0</testcontainers.version>
</properties>

<dependency>
  <groupId>org.testcontainers</groupId>
  <artifactId>junit-jupiter</artifactId>
  <scope>test</scope>
</dependency>

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

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.testcontainers</groupId>
      <artifactId>testcontainers-bom</artifactId>
      <version>${testcontainers.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
Örnek
Eğer dependencyManagement kullanmak istemiyorsak şöyle yaparız
<dependency>
   <groupId>org.testcontainers</groupId>
   <artifactId>testcontainers</artifactId>
   <version>1.16.3</version>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.testcontainers</groupId>
   <artifactId>junit-jupiter</artifactId>
   <version>1.16.3</version>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>org.testcontainers</groupId>
   <artifactId>postgresql</artifactId>
   <version>1.16.3</version>
   <scope>test</scope>
</dependency>
TestContainer ile PostgreSQL kullanmak için iki yöntem var.

1. Test veri tabanı bilgisi src/test/resources/application.yml altında saklanır
2. Teste @Container anotasyonu eklenir
3. TestContainer Sınıfından Kalıtma

1. Yml Kullanımı
Örnek
Şöyle yaparız
limizde şöyle bir application.yml olsun. Burada jdbc connection string "tc" ile başlıyor.
spring.datasource.url=jdbc:tc:postgresql://localhost/testdb
spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.datasource.username=user
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
Örnek
Elimizde şöyle bir application.yml olsun. Burada jdbc connection string "tc" ile başlıyor.
spring:
  datasource:
    url: jdbc:tc:postgresql:9.6.8:///test_database
    username: user
    password: password
  jpa:
    hibernate:
      ddl-auto: create
Açıklaması şöyle
Have you noticed the tc suffix in the JDBC-connection string? That’s the magic that comes with the union of JUnit 5 and Test containers. The thing is that you don’t need any programmatic configurations at all! When the framework sees that url contains the tc suffix it runs all necessary Docker commands internally.
Elimizde şöyle bir service kodu olsun
@Service
@RequiredArgsConstructor
public class PersonCreateServiceImpl implements PersonCreateService {

  private final PersonValidateService personValidateService;
  private final PersonRepository personRepository;

  @Override
  @Transactional
  public List<PersonDTO> createFamily(Iterable<String> firstNames, String lastName) {
    final var people = new ArrayList<PersonDTO>();
    firstNames.forEach(firstName -> people.add(createPerson(firstName, lastName)));
    return people;
  }
  
  @Override
  @Transactional
  public PersonDTO createPerson(String firstName, String lastName) {
    personValidateService.checkUserCreation(firstName, lastName);
    final var createdPerson = personRepository.saveAndFlush(
        new Person()
            .setFirstName(firstName)
            .setLastName(lastName)
    );
    return DTOConverters.toPersonDTO(createdPerson);
  }
}
Bu service koduna H2 veri tabanı ile test yazsaydık şöyle olurdu. Burada repository sınıfı ve service sınıfı @Autowired ile aynen kullanılıyor. Service sınıfının kullandığı PersonValidateService ile mock'lanıyor. @AutoConfigureTestDatabase ile test için veri tabanı yaratılıyor.
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
@AutoConfigureTestDatabase
class PersonCreateServiceImplSpringBootTest {
  @Autowired
  private PersonRepository personRepository;
  @MockBean
  private PersonValidateService personValidateService;
  @Autowired
  private PersonCreateService personCreateService;

  @BeforeEach
  void init() {
    personRepository.deleteAll();
  }

  @Test
  void shouldCreateOnePerson() {
    final var people = personCreateService.createFamily(List.of("Simon"),"Kirekov");
    assertEquals(1, people.size());
    final var person = people.get(0);
    assertEquals("Simon", person.getFirstName());
    assertEquals("Kirekov", person.getLastName());
    assertTrue(person.getDateCreated().isBefore(ZonedDateTime.now()));
  }

  @Test
  void shouldRollbackIfAnyUserIsNotValidated() {
    doThrow(new ValidationFailedException(""))
        .when(personValidateService)
        .checkUserCreation("John", "Brown");
    assertThrows(ValidationFailedException.class, () -> personCreateService.createFamily(
        List.of("Matilda", "Vasya", "John"),
        "Brown"
    ));
    assertEquals(0, personRepository.count());
  }
}
Bu testi TestContainers ile çalışacak hale getirmek için şöyle yaparız. ActiveProfiles ile yaml dosyası kullanılır.
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
@Testcontainers
@ActiveProfiles("test-containers")
class PersonCreateServiceImplTestContainers {
  @Autowired
  private PersonRepository personRepository;
  @MockBean
  private PersonValidateService personValidateService;
  @Autowired
  private PersonCreateService personCreateService;

  @BeforeEach
  void init() {
    personRepository.deleteAll();
  }
  @Test
  void shouldCreateOnePerson() {
    final var people = personCreateService.createFamily(
        List.of("Simon"),"Kirekov");
    assertEquals(1, people.size());
    final var person = people.get(0);
    assertEquals("Simon", person.getFirstName());
    assertEquals("Kirekov", person.getLastName());
    assertTrue(person.getDateCreated().isBefore(ZonedDateTime.now()));
  }
  @Test
  void shouldRollbackIfAnyUserIsNotValidated() {
    doThrow(new ValidationFailedException(""))
        .when(personValidateService)
        .checkUserCreation("John", "Brown");
    assertThrows(ValidationFailedException.class, () -> personCreateService.createFamily(
        List.of("Matilda", "Vasya", "John"),
        "Brown"
    ));
    assertEquals(0, personRepository.count());
  }
}
2. @Container Kullanımı
Repository sınıfı @Autowire ile test'e dahil edilir.

Örnek 
Şöyle yaparız. Burada PostgreSQLContainer() sürümü veriliyor
// JUnit 5 example with Spring Boot >= 2.2.6
@Testcontainers
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationIT {

  @Container
  public static PostgreSQLContainer container = new PostgreSQLContainer("postgres:11.1")
    .withPassword("inmemory")
    .withUsername("inmemory");

  @DynamicPropertySource
  static void postgresqlProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", container::getJdbcUrl);
    registry.add("spring.datasource.password", container::getPassword);
    registry.add("spring.datasource.username", container::getUsername);
  }
  ...
}
Eğer DataSource ta yaratmak istersek şöyle yaparız
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Testcontainers
@Import(Configuration.class)
public abstract class BaseApiTest {

  @Container
  static final PostgreSQLContainer<?> postgres = 
    new PostgreSQLContainer<>("postgres:10.9");
 
  @DynamicPropertySource
  static void properties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgres::getJdbcUrl);
    registry.add("spring.datasource.username", postgres::getUsername);
    registry.add("spring.datasource.password", postgres::getPassword);
 
  }

  @TestConfiguration
  public static class Configuration {

    @Lazy
    @Bean("testSetupDataSource")
    public DataSource testInitDataSource() {
      PGSimpleDataSource ds = new PGSimpleDataSource();
      ds.setUrl(postgres.getJdbcUrl());
      ds.setDatabaseName(postgres.getDatabaseName());
      ds.setUser(postgres.getUsername());
      ds.setPassword(postgres.getPassword());
      return ds;
    }
  }
}
Örnek
Şöyle yaparız
@DataJdbcTest
@Import(DatasourceConfig.class) @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) @Testcontainers @DisplayName("Comment Repository Integration Tests With TestContainers") class CommentRepositoryTest { @Container static PostgreSQLContainer<?> postgresContainer = new PostgreSQLContainer<>(DockerImageName.parse("postgres:15.1")); @DynamicPropertySource static void overridePostgresProperties(DynamicPropertyRegistry registry) { registry.add("spring.datasource.url", postgresContainer::getJdbcUrl); registry.add("spring.datasource.username", postgresContainer::getUsername); registry.add("spring.datasource.password", postgresContainer::getPassword); registry.add("spring.flyway.url", postgresContainer::getJdbcUrl); } @Autowired private CommentRepository commentRepository; ... }
Açıklaması şöyle
@DataJdbcTest focuses only on Data JDBC components.
@Import(DatasourceConfig.class) provides JDBC auditing support.
@AutoConfigureTestDatabase disables in-memory test databases with AutoConfigureTestDatabase.Replace.NONE value. Because we want to use real database instance instead of in-memory database.
Örnek
Şöyle yaparız. Liquibase veri tabanını test için doldurur
@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes =
ContainerSpringAfter226Test.class)
@EnableAutoConfiguration
public class ContainerSpringAfter226Test {

  @Container
  public static PostgreSQLContainer postgreSQLContainer =
SharedPostgresSQLContainer.getInstance()
    .withDatabaseName("dbname")
    .withUsername("user")
    .withPassword("password");

  @DynamicPropertySource
  public static void postgreSQLProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
    registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
    registry.add("spring.datasource.password", postgreSQLContainer::getPassword);

    registry.add("spring.liquibase.url", postgreSQLContainer::getJdbcUrl);
    registry.add("spring.liquibase.user", postgreSQLContainer::getUsername);
    registry.add("spring.liquibase.password", postgreSQLContainer::getPassword);
    registry.add("spring.liquibase.change-log",
() -> "classpath:/db/changelog/db.changelog-master.xml");
  }
  ...
}
3. TestContainer Sınıfından Kalıtma
Örnek
Şöyle yaparız
@TestConfiguration
public class PostgresSQLTestContainerConfig
  extends PostgreSQLContainer<PostgresSQLTestContainerConfig> {
  private static final String DOCKER_IMAGE_VERSION = "postgres:latest";

  private static final PostgresSQLTestContainerConfig POSTGRES_SQL_TEST_CONTAINER =
    new PostgresSQLTestContainerConfig().withReuse(true);

  static {
    POSTGRES_SQL_TEST_CONTAINER.start();
    System.setProperty("spring.datasource.url", POSTGRES_SQL_TEST_CONTAINER.getJdbcUrl());
    System.setProperty("spring.datasource.username",
      POSTGRES_SQL_TEST_CONTAINER.getUsername());
    System.setProperty("spring.datasource.password",
      POSTGRES_SQL_TEST_CONTAINER.getPassword());
  }

  public PostgresSQLTestContainerConfig() {
    super(DOCKER_IMAGE_VERSION);
  }

  @Override
  public void stop() {
    // do nothing, JVM handles shut down
  }
}


Hiç yorum yok:

Yorum Gönder