diff --git a/modules/02-spring-boot-examples/build.gradle.kts b/modules/02-spring-boot-examples/build.gradle.kts index 001723a..86ade68 100644 --- a/modules/02-spring-boot-examples/build.gradle.kts +++ b/modules/02-spring-boot-examples/build.gradle.kts @@ -25,6 +25,8 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") // Testcontainers deps + testImplementation("org.testcontainers:postgresql") + testImplementation("org.testcontainers:junit-jupiter") testImplementation("org.junit.jupiter:junit-jupiter-params") // Other (Optional) diff --git a/modules/02-spring-boot-examples/src/test/java/.gitkeep b/modules/02-spring-boot-examples/src/test/java/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/StandaloneBasicIT.java b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/StandaloneBasicIT.java new file mode 100644 index 0000000..54dc5c3 --- /dev/null +++ b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/StandaloneBasicIT.java @@ -0,0 +1,92 @@ +package com.maeemresen.testcontainers.workshop.spring; + +import com.maeemresen.testcontainers.workshop.spring.domain.Person; +import com.maeemresen.testcontainers.workshop.spring.repository.PersonRepository; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.ContainerState; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Slf4j +@Testcontainers +@SpringBootTest +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +class StandaloneBasicIT { + + @Container + static final PostgreSQLContainer POSTGRESQL_CONTAINER = new PostgreSQLContainer<>("postgres:15.1"); + + static { + POSTGRESQL_CONTAINER.withDatabaseName("integration-tests-db"); + POSTGRESQL_CONTAINER.withUsername("sa"); + POSTGRESQL_CONTAINER.withPassword("sa"); + } + + @DynamicPropertySource + static void overrideProperties(DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", POSTGRESQL_CONTAINER::getJdbcUrl); + registry.add("spring.datasource.username", POSTGRESQL_CONTAINER::getUsername); + registry.add("spring.datasource.password", POSTGRESQL_CONTAINER::getPassword); + registry.add("spring.jpa.hibernate.ddl-auto", () -> "create"); + } + + @Autowired + private PersonRepository personRepository; + + @BeforeEach + void init(final TestInfo testInfo) { + final String containerId = Optional.of(POSTGRESQL_CONTAINER) + .filter(ContainerState::isRunning) + .map(GenericContainer::getContainerId) + .orElse("non-exists"); + final String containerInfo = String.format("Container[%s]=%s", "PostgreSQL Container", containerId); + log.info("TEST:{} is using {}", testInfo.getDisplayName(), containerInfo); + } + + private Person savePerson(String name) { + var person = new Person(); + person.setName(name); + return personRepository.save(person); + } + + private String randomName() { + return "name-" + System.currentTimeMillis(); + } + + @Test + @DisplayName("Standalone Test1") + @Order(1) + void whenFindByNameIgnoreCase_thenReturnPerson() { + String name = randomName(); + Person savedPerson = savePerson(name); + var findPerson = personRepository.findTopByNameIgnoreCase(name.toUpperCase()); + + assertTrue(findPerson.isPresent(), "Person is not found"); + assertEquals(savedPerson.getId(), findPerson.get().getId()); + } + + @Test + @DisplayName("Standalone Test2") + @Order(2) + void whenFindByNameNonExistingName_thenReturnEmpty() { + String name = randomName(); + String nonExistingName = "non-existing-name"; + + savePerson(name); + var findPerson = personRepository.findTopByNameIgnoreCase(nonExistingName); + + assertTrue(findPerson.isEmpty(), "Person is found"); + } +} diff --git a/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/restartable/AbstractRestartWithContextPostgresIT.java b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/restartable/AbstractRestartWithContextPostgresIT.java new file mode 100644 index 0000000..0620e18 --- /dev/null +++ b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/restartable/AbstractRestartWithContextPostgresIT.java @@ -0,0 +1,54 @@ +package com.maeemresen.testcontainers.workshop.spring.restartable; + +import com.maeemresen.testcontainers.workshop.spring.util.ContainerFactory; +import com.maeemresen.testcontainers.workshop.spring.util.ContainerHolder; +import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.TestInfo; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.Ordered; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.PostgreSQLContainer; + +@Slf4j +@ContextConfiguration(initializers = AbstractRestartWithContextPostgresIT.ContextInitializer.class) +@SpringBootTest +public abstract class AbstractRestartWithContextPostgresIT { + + public static class ContextInitializer implements ApplicationContextInitializer, Ordered { + @Override + public void initialize(@NotNull ConfigurableApplicationContext applicationContext) { + try { + GLOBAL_POSTGRESQL_CONTAINER.restart(); + } catch (Exception e) { + log.error("Error while restarting the PostgreSQL container", e); + } + } + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + } + + protected static final ContainerHolder> GLOBAL_POSTGRESQL_CONTAINER + = ContainerFactory.getIntegrationTestDbContainer("RestartWithContext Postgres Container"); + + @DynamicPropertySource + static void overrideProperties(DynamicPropertyRegistry registry) { + final var container = GLOBAL_POSTGRESQL_CONTAINER.getContainer(); + registry.add("spring.datasource.url", container::getJdbcUrl); + registry.add("spring.datasource.username", container::getUsername); + registry.add("spring.datasource.password", container::getPassword); + } + + @AfterEach + void init(final TestInfo testInfo) { + log.info("Executed {} successfully.", testInfo.getDisplayName()); + } +} diff --git a/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/restartable/RestartableIT.java b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/restartable/RestartableIT.java new file mode 100644 index 0000000..7164b22 --- /dev/null +++ b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/restartable/RestartableIT.java @@ -0,0 +1,56 @@ +package com.maeemresen.testcontainers.workshop.spring.restartable; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.*; +import org.springframework.test.annotation.DirtiesContext; + +@Slf4j +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +class RestartableIT { + + @BeforeEach + void init(final TestInfo testInfo) { + log.info("TEST: {} is using {}", + testInfo.getDisplayName(), + AbstractRestartWithContextPostgresIT.GLOBAL_POSTGRESQL_CONTAINER.toString()); + } + + @Nested + @Order(1) + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class Suit1 extends AbstractRestartWithContextPostgresIT { + + @Test + @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) + @DisplayName("Restartable Suit1 - Test1") + @Order(1) + void restartable1Test1() { + } + + + @Test + @DisplayName("Restartable Suit1 - Test2") + @Order(2) + void restartable1Test2() { + } + } + + @Nested + @Order(2) + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class Suit2 extends AbstractRestartWithContextPostgresIT { + @Test + @DisplayName("Restartable Suit2 - Test1") + @Order(1) + void restartable2Test1() { + + } + + @Test + @DisplayName("Restartable Suit2 - Test2") + @Order(2) + void restartable2Test2() { + + } + } +} diff --git a/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/singletion/AbstractSingletonPostgresIT.java b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/singletion/AbstractSingletonPostgresIT.java new file mode 100644 index 0000000..eb3a257 --- /dev/null +++ b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/singletion/AbstractSingletonPostgresIT.java @@ -0,0 +1,36 @@ +package com.maeemresen.testcontainers.workshop.spring.singletion; + +import com.maeemresen.testcontainers.workshop.spring.util.ContainerHolder; +import com.maeemresen.testcontainers.workshop.spring.util.ContainerFactory; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.TestInfo; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.PostgreSQLContainer; + +@Slf4j +@SpringBootTest +public abstract class AbstractSingletonPostgresIT { + + protected static final ContainerHolder> GLOBAL_POSTGRESQL_CONTAINER + = ContainerFactory.getIntegrationTestDbContainer("Singleton Postgres Container"); + + static { + GLOBAL_POSTGRESQL_CONTAINER.start(); + } + + @DynamicPropertySource + static void overrideProperties(DynamicPropertyRegistry registry) { + final var container = GLOBAL_POSTGRESQL_CONTAINER.getContainer(); + registry.add("spring.datasource.url", container::getJdbcUrl); + registry.add("spring.datasource.username", container::getUsername); + registry.add("spring.datasource.password", container::getPassword); + } + + @AfterEach + void init(final TestInfo testInfo){ + log.info("Executed {} successfully.", testInfo.getDisplayName()); + } +} diff --git a/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/singletion/SingletonIT.java b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/singletion/SingletonIT.java new file mode 100644 index 0000000..298342b --- /dev/null +++ b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/singletion/SingletonIT.java @@ -0,0 +1,56 @@ +package com.maeemresen.testcontainers.workshop.spring.singletion; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.*; +import org.springframework.test.annotation.DirtiesContext; + +@Slf4j +@TestClassOrder(ClassOrderer.OrderAnnotation.class) +class SingletonIT { + + @BeforeEach + void init(final TestInfo testInfo) { + log.info("TEST:{} is using {}", + testInfo.getDisplayName(), + AbstractSingletonPostgresIT.GLOBAL_POSTGRESQL_CONTAINER.toString()); + } + + @Nested + @Order(1) + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class Suit1 extends AbstractSingletonPostgresIT { + + @Test + @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) + @DisplayName("Singleton Suit1 - Test1") + @Order(1) + void singleton1Test1() { + } + + + @Test + @DisplayName("Singleton Suit1 - Test2") + @Order(2) + void singleton1Test2() { + } + } + + @Nested + @Order(2) + @TestMethodOrder(MethodOrderer.OrderAnnotation.class) + class Suit2 extends AbstractSingletonPostgresIT { + @Test + @DisplayName("Singleton Suit2 - Test1") + @Order(1) + void singleton2Test1() { + + } + + @Test + @DisplayName("Singleton Suit2 - Test2") + @Order(2) + void singleton2Test2() { + + } + } +} diff --git a/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/util/ContainerFactory.java b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/util/ContainerFactory.java new file mode 100644 index 0000000..bd85a82 --- /dev/null +++ b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/util/ContainerFactory.java @@ -0,0 +1,21 @@ +package com.maeemresen.testcontainers.workshop.spring.util; + +import lombok.experimental.UtilityClass; +import org.testcontainers.containers.PostgreSQLContainer; + +@UtilityClass +public class ContainerFactory { + public static > ContainerHolder> getIntegrationTestDbContainer(final String name) { + try (var container = new PostgreSQLContainer("postgres:13.3")) { + container.withDatabaseName("integration-tests-db"); + container.withUsername("sa"); + container.withPassword("sa"); + return ContainerHolder + .>builder() + .name(name) + .container(container) + .build(); + } + } + +} diff --git a/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/util/ContainerHolder.java b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/util/ContainerHolder.java new file mode 100644 index 0000000..45e2a6c --- /dev/null +++ b/modules/02-spring-boot-examples/src/test/java/com/maeemresen/testcontainers/workshop/spring/util/ContainerHolder.java @@ -0,0 +1,55 @@ +package com.maeemresen.testcontainers.workshop.spring.util; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.testcontainers.containers.ContainerState; +import org.testcontainers.containers.GenericContainer; + +import java.util.Optional; + +@Slf4j +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@Builder +public class ContainerHolder> { + + private final String name; + + @Getter + private final T container; + + public void restart() { + stop(); + start(); + } + + public void start() { + log.trace("{} is starting", name); + container.start(); + log.debug("{} started with instance {}", name, container.getContainerId()); + } + + public void stop() { + if (container.isCreated() || container.isRunning()) { + log.trace("{} has running instance with id {}. Stopping it.", + name, + container.getContainerId()); + final var containerId = container.getContainerId(); + container.stop(); + log.debug("{} instance stopped.", containerId); + } else { + log.debug("{} has not any running instance to stop.", name); + } + } + + @Override + public String toString() { + final String containerId = Optional.ofNullable(container) + .filter(ContainerState::isRunning) + .map(GenericContainer::getContainerId) + .orElse("non-exists"); + return String.format("Container[%s]=%s", name, containerId); + } +} \ No newline at end of file