diff --git a/build.gradle b/build.gradle index 7f5da45c9..2002a34e4 100644 --- a/build.gradle +++ b/build.gradle @@ -24,10 +24,14 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'com.mysql:mysql-connector-j' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.testcontainers:testcontainers:1.19.3' + testImplementation 'org.testcontainers:junit-jupiter:1.19.3' + testImplementation 'org.testcontainers:mysql' testImplementation 'io.rest-assured:rest-assured:5.3.2' testImplementation 'org.springframework.boot:spring-boot-starter-test' } diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml new file mode 100644 index 000000000..7212038ca --- /dev/null +++ b/src/main/resources/application-test.yml @@ -0,0 +1,4 @@ +spring: + jpa: + hibernate: + ddl-auto: create diff --git a/src/test/java/in/koreatech/koin/AcceptanceTest.java b/src/test/java/in/koreatech/koin/AcceptanceTest.java new file mode 100644 index 000000000..e57d21854 --- /dev/null +++ b/src/test/java/in/koreatech/koin/AcceptanceTest.java @@ -0,0 +1,58 @@ +package in.koreatech.koin; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +import in.koreatech.koin.support.DBInitializer; +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.DynamicPropertyRegistry; +import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.junit.jupiter.Container; + +@SpringBootTest(webEnvironment = RANDOM_PORT) +@Import(DBInitializer.class) +@ActiveProfiles("test") +public abstract class AcceptanceTest { + + private static final String ROOT = "test"; + private static final String ROOT_PASSWORD = "1234"; + + @LocalServerPort + protected int port; + + @Autowired + private DBInitializer dataInitializer; + + @Container + protected static MySQLContainer container; + + @DynamicPropertySource + private static void configureProperties(final DynamicPropertyRegistry registry) { + registry.add("spring.datasource.url", container::getJdbcUrl); + registry.add("spring.datasource.username", () -> ROOT); + registry.add("spring.datasource.password", () -> ROOT_PASSWORD); + } + + static { + container = (MySQLContainer) new MySQLContainer("mysql:5.7.34") + .withDatabaseName("test") + .withUsername(ROOT) + .withPassword(ROOT_PASSWORD) + .withCommand("--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci"); + container.start(); + } + + @BeforeEach + void delete() { + if (RestAssured.port == RestAssured.UNDEFINED_PORT) { + RestAssured.port = port; + } + dataInitializer.clear(); + } +} diff --git a/src/test/java/in/koreatech/koin/acceptance/TrackApiTest.java b/src/test/java/in/koreatech/koin/acceptance/TrackApiTest.java index c11cb9e57..afb96932f 100644 --- a/src/test/java/in/koreatech/koin/acceptance/TrackApiTest.java +++ b/src/test/java/in/koreatech/koin/acceptance/TrackApiTest.java @@ -1,8 +1,6 @@ package in.koreatech.koin.acceptance; -import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; -import static org.springframework.test.annotation.DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD; - +import in.koreatech.koin.AcceptanceTest; import in.koreatech.koin.domain.Member; import in.koreatech.koin.domain.TechStack; import in.koreatech.koin.domain.Track; @@ -14,21 +12,12 @@ import io.restassured.response.Response; import java.time.format.DateTimeFormatter; import org.assertj.core.api.SoftAssertions; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.server.LocalServerPort; import org.springframework.http.HttpStatus; -import org.springframework.test.annotation.DirtiesContext; - -@SpringBootTest(webEnvironment = RANDOM_PORT) -@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD) -class TrackApiTest { - @LocalServerPort - int port; +class TrackApiTest extends AcceptanceTest { @Autowired private TrackRepository trackRepository; @@ -39,11 +28,6 @@ class TrackApiTest { @Autowired private MemberRepository memberRepository; - @BeforeEach - void setUp() { - RestAssured.port = port; - } - @Test @DisplayName("BCSDLab 트랙 정보를 조회한다") void findTracks() { @@ -119,7 +103,8 @@ void findTrack() { softly.assertThat(response.body().jsonPath().getString("TrackName")).isEqualTo(track.getName()); softly.assertThat(response.body().jsonPath().getList("Members")).hasSize(1); - softly.assertThat(response.body().jsonPath().getInt("Members[0].id")).isEqualTo(member.getId()); + softly.assertThat(response.body().jsonPath().getInt("Members[0].id")) + .isEqualTo(member.getId().longValue()); softly.assertThat(response.body().jsonPath().getString("Members[0].name")).isEqualTo(member.getName()); softly.assertThat(response.body().jsonPath().getString("Members[0].student_number")) .isEqualTo(member.getStudentNumber()); diff --git a/src/test/java/in/koreatech/koin/support/DBInitializer.java b/src/test/java/in/koreatech/koin/support/DBInitializer.java new file mode 100644 index 000000000..dfcfd2b28 --- /dev/null +++ b/src/test/java/in/koreatech/koin/support/DBInitializer.java @@ -0,0 +1,61 @@ +package in.koreatech.koin.support; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.List; +import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestComponent; +import org.springframework.transaction.annotation.Transactional; + +@TestComponent +public class DBInitializer { + + private static final int OFF = 0; + private static final int ON = 1; + private static final int COLUMN_INDEX = 1; + + private final List tableNames = new ArrayList<>(); + + @Autowired + private DataSource dataSource; + + @PersistenceContext + private EntityManager entityManager; + + private void findDatabaseTableNames() { + try (final Statement statement = dataSource.getConnection().createStatement()) { + ResultSet resultSet = statement.executeQuery("SHOW TABLES"); + while (resultSet.next()) { + final String tableName = resultSet.getString(COLUMN_INDEX); + tableNames.add(tableName); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void truncate() { + setForeignKeyCheck(OFF); + for (String tableName : tableNames) { + entityManager.createNativeQuery(String.format("TRUNCATE TABLE %s", tableName)).executeUpdate(); + } + setForeignKeyCheck(ON); + } + + private void setForeignKeyCheck(int mode) { + entityManager.createNativeQuery(String.format("SET FOREIGN_KEY_CHECKS = %d", mode)).executeUpdate(); + } + + @Transactional + public void clear() { + if (tableNames.isEmpty()) { + findDatabaseTableNames(); + } + entityManager.clear(); + truncate(); + } +}