Skip to content

Commit

Permalink
feat: Jooq 테스트와 TestContainer 설정을 추가합니다 (#33)
Browse files Browse the repository at this point in the history
* feat: testcontainers 의존성 추가

* feat: testcontainers를 위한 docker-compose 추가

* feat: RepoTestContainerInitializer 구현

* feat: JooqTestConfig 추가

* feat: JooqTestSpec 추가

* feat: RepoTestContainerInitializer 적용

* feat: application-test 구성 추가

* test: Jooq 사용 예시를 위한 _SampleJooqTest 구현

* refactor: 변경된 스키마에 맞추어 테스트 수정
  • Loading branch information
belljun3395 authored Jun 17, 2024
1 parent a7222fa commit 418cde4
Show file tree
Hide file tree
Showing 10 changed files with 294 additions and 0 deletions.
4 changes: 4 additions & 0 deletions api-repo/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ dependencies {
implementation("org.jooq:jooq-meta:${DependencyVersion.JOOQ}")
implementation("org.jooq:jooq-codegen:${DependencyVersion.JOOQ}")
jooqCodegen("org.jooq:jooq-meta-extensions:${DependencyVersion.JOOQ}")

/** test container */
implementation(platform("org.testcontainers:testcontainers-bom:${DependencyVersion.TEST_CONTAINER}"))
testImplementation("org.testcontainers:mysql")
}

/** copy data migration */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.few.api.repo

import org.springframework.context.ApplicationContextInitializer
import org.springframework.context.ConfigurableApplicationContext
import org.testcontainers.containers.DockerComposeContainer
import java.io.File

class RepoTestContainerInitializer : ApplicationContextInitializer<ConfigurableApplicationContext> {
private val log: org.slf4j.Logger =
org.slf4j.LoggerFactory.getLogger(RepoTestContainerInitializer::class.java)

companion object {
private const val MYSQL = "mysql"
private const val MYSQL_PORT = 3306

private val dockerCompose =
DockerComposeContainer(File("src/test/resources/docker-compose.yml"))
.withExposedService(MYSQL, MYSQL_PORT)
}

override fun initialize(applicationContext: ConfigurableApplicationContext) {
log.debug("===== set up test containers =====")

dockerCompose.start()

log.debug("===== success set up test containers =====")
}
}
11 changes: 11 additions & 0 deletions api-repo/src/test/kotlin/com/few/api/repo/jooq/JooqTestConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.few.api.repo.jooq

import com.few.api.repo.config.ApiRepoConfig
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.test.context.TestConfiguration
import org.springframework.context.annotation.ComponentScan

@TestConfiguration
@EnableAutoConfiguration
@ComponentScan(basePackages = [ApiRepoConfig.BASE_PACKAGE])
class JooqTestConfig
12 changes: 12 additions & 0 deletions api-repo/src/test/kotlin/com/few/api/repo/jooq/JooqTestSpec.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.few.api.repo.jooq

import com.few.api.repo.RepoTestContainerInitializer
import com.few.api.repo.config.ApiRepoConfig
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.context.ActiveProfiles
import org.springframework.test.context.ContextConfiguration

@ActiveProfiles("new", "test", "api-repo-local")
@SpringBootTest(classes = [ApiRepoConfig::class])
@ContextConfiguration(initializers = [RepoTestContainerInitializer::class])
abstract class JooqTestSpec
194 changes: 194 additions & 0 deletions api-repo/src/test/kotlin/com/few/api/repo/jooq/_SampleJooqTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package com.few.api.repo.jooq

import jooq.jooq_dsl.tables.Member
import org.jooq.DSLContext
import org.jooq.JSON
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.dao.DataIntegrityViolationException
import org.springframework.dao.DuplicateKeyException
import org.springframework.test.annotation.Rollback
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDateTime

class _SampleJooqTest : JooqTestSpec() {

private val log: org.slf4j.Logger = LoggerFactory.getLogger(_SampleJooqTest::class.java)

@Autowired
private lateinit var dslContext: DSLContext

companion object {
const val EMAIL = "[email protected]"
const val TYPECD: Byte = 1
}

@BeforeEach
fun setUp() {
log.debug("===== start setUp =====")
dslContext.deleteFrom(Member.MEMBER).execute()
dslContext.insertInto(Member.MEMBER)
.set(Member.MEMBER.EMAIL, EMAIL)
.set(Member.MEMBER.TYPE_CD, TYPECD)
.execute()
log.debug("===== finish setUp =====")
}

@Test
@Transactional
fun `새로운 정보를 저장합니다`() {
// given
val email = "[email protected]"
val typeCd: Byte = 1

// when
val result = dslContext.insertInto(Member.MEMBER)
.set(Member.MEMBER.EMAIL, email)
.set(Member.MEMBER.TYPE_CD, typeCd)
.execute()

// then
assert(result > 0)
}

@Test
@Transactional
fun `이메일이 중복되는 경우 저장에 실패합니다`() {
// when & then
assertThrows<DuplicateKeyException> {
dslContext.insertInto(Member.MEMBER)
.set(Member.MEMBER.EMAIL, EMAIL)
.set(Member.MEMBER.TYPE_CD, TYPECD)
.execute()
}
}

@Test
@Transactional
fun `이메일 값을 입력하지 않은면 저장에 실패합니다`() {
// when & then
assertThrows<DataIntegrityViolationException> {
dslContext.insertInto(Member.MEMBER)
.set(Member.MEMBER.TYPE_CD, TYPECD)
.execute()
}
}

@Test
@Transactional
fun `타입 코드 값을 입력하지 않은면 저장에 실패합니다`() {
// when & then
assertThrows<DataIntegrityViolationException> {
dslContext.insertInto(Member.MEMBER)
.set(Member.MEMBER.EMAIL, EMAIL)
.execute()
}
}

@Test
fun `이메일 일치 조건을 통해 정보를 조회합니다`() {
// when
val result = dslContext.selectFrom(Member.MEMBER)
.where(Member.MEMBER.EMAIL.eq(EMAIL))
.and(Member.MEMBER.DELETED_AT.isNull())
.fetchOne()

// then
assert(result != null)
assert(result!!.email == EMAIL)
assert(result.typeCd == TYPECD)
assert(result.description.equals(JSON.json("{}")))
assert(result.createdAt != null)
assert(result.deletedAt == null)
}

@Test
fun `이메일 불일치 조건을 통해 유저를 조회합니다`() {
// when
val result = dslContext.selectFrom(Member.MEMBER)
.where(Member.MEMBER.EMAIL.ne("[email protected]"))
.and(Member.MEMBER.DELETED_AT.isNull())
.fetch()

// then
assert(result.isNotEmpty())
}

@Test
@Transactional
fun `이메일을 수정합니다`() {
// given
val newEmail = "[email protected]"

// when
val update = dslContext.update(Member.MEMBER)
.set(Member.MEMBER.EMAIL, newEmail)
.where(Member.MEMBER.EMAIL.eq(EMAIL))
.and(Member.MEMBER.DELETED_AT.isNull())
.execute()

val result = dslContext.selectFrom(Member.MEMBER)
.where(Member.MEMBER.EMAIL.eq(newEmail))
.and(Member.MEMBER.DELETED_AT.isNull())
.fetchOne()

// then
assert(update > 0)
assert(result != null)
assert(result!!.email == newEmail)
}

@Test
@Transactional
fun `타입 코드를 수정합니다`() {
// given
val newTypeCd: Byte = 2

// when
val update = dslContext.update(Member.MEMBER)
.set(Member.MEMBER.TYPE_CD, newTypeCd)
.where(Member.MEMBER.EMAIL.eq(EMAIL))
.and(Member.MEMBER.DELETED_AT.isNull())
.execute()

val result = dslContext.selectFrom(Member.MEMBER)
.where(Member.MEMBER.EMAIL.eq(EMAIL))
.and(Member.MEMBER.DELETED_AT.isNull())
.fetchOne()

// then
assert(update > 0)
assert(result != null)
assert(result!!.typeCd == newTypeCd)
}

@Test
@Rollback(false)
@Transactional
fun `소프트 삭제를 수행합니다`() {
// given
val deleteTarget = dslContext.selectFrom(Member.MEMBER)
.where(Member.MEMBER.EMAIL.eq(EMAIL))
.and(Member.MEMBER.DELETED_AT.isNull())
.fetchOne()

// when
val softDelete = dslContext.update(Member.MEMBER)
.set(Member.MEMBER.DELETED_AT, LocalDateTime.now())
.where(Member.MEMBER.EMAIL.eq(EMAIL))
.and(Member.MEMBER.DELETED_AT.isNull())
.execute()

val result = dslContext.selectFrom(Member.MEMBER)
.where(Member.MEMBER.EMAIL.eq(EMAIL))
.fetchOne()

// then
assert(deleteTarget != null)
assert(softDelete > 0)
assert(result != null)
}
}
6 changes: 6 additions & 0 deletions api-repo/src/test/resources/application-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
logging:
level:
org.jooq: DEBUG
org.springframework.jdbc: DEBUG
com.few.api.repo: DEBUG
org.testcontainers: INFO
14 changes: 14 additions & 0 deletions api-repo/src/test/resources/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: '3.7'

services:
mysql:
image: mysql/mysql-server:8.0.27
environment:
- MYSQL_ROOT_PASSWORD=root
- MYSQL_ROOT_HOST=%
- TZ=Asia/Seoul
command: [ "--character-set-server=utf8mb4", "--collation-server=utf8mb4_unicode_ci", "--lower_case_table_names=1", "--max_connections=2048", "--wait_timeout=3600" ]
ports:
- "13306:3306"
volumes:
- ./mysql-init.d:/docker-entrypoint-initdb.d
13 changes: 13 additions & 0 deletions api-repo/src/test/resources/mysql-init.d/00_init.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE
USER 'few-test-local'@'localhost' IDENTIFIED BY 'few-test-local';
CREATE
USER 'few-test-local'@'%' IDENTIFIED BY 'few-test-local';

GRANT ALL PRIVILEGES ON *.* TO
'few-test-local'@'localhost';
GRANT ALL PRIVILEGES ON *.* TO
'few-test-local'@'%';

CREATE
DATABASE api DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/DependencyVersion.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ object DependencyVersion {
const val KOTEST = "5.8.0"
const val KOTEST_EXTENSION = "1.1.3"
const val COROUTINE_TEST = "1.8.0"
const val TEST_CONTAINER = "1.19.8"

/** docs */
const val ASCIIDOCTOR = "3.3.2"
Expand Down
11 changes: 11 additions & 0 deletions data/db/migration/entity/V1.00.0.0__draft_table_design.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- 작가 및 유저
CREATE TABLE users
(
id BIGINT NOT NULL AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
type_cd TINYINT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted_at TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE (email)
);

0 comments on commit 418cde4

Please sign in to comment.