From 29a8e834b91d84aa38d1b74f668a02b70570dd61 Mon Sep 17 00:00:00 2001 From: pricelees Date: Thu, 10 Jul 2025 12:17:52 +0900 Subject: [PATCH] feat: implement application for testing --- build.gradle.kts | 62 ++++++++++--------- .../demo/DataSourceLookupController.kt | 27 ++++++++ .../kotlin/com/sangdol/demo/DemoController.kt | 52 ++++++++++++++++ .../kotlin/com/sangdol/demo/DemoEntity.kt | 20 ++++++ .../kotlin/com/sangdol/demo/DemoRepository.kt | 6 ++ .../kotlin/com/sangdol/demo/DemoService.kt | 26 ++++++++ src/main/resources/application-deploy.yml | 8 +++ src/main/resources/application-local.yml | 15 +++++ src/main/resources/application.yml | 9 +++ .../com/sangdol/demo/DemoApplicationTests.kt | 13 ---- .../com/sangdol/demo/DemoServiceTest.kt | 43 +++++++++++++ 11 files changed, 239 insertions(+), 42 deletions(-) create mode 100644 src/main/kotlin/com/sangdol/demo/DataSourceLookupController.kt create mode 100644 src/main/kotlin/com/sangdol/demo/DemoController.kt create mode 100644 src/main/kotlin/com/sangdol/demo/DemoEntity.kt create mode 100644 src/main/kotlin/com/sangdol/demo/DemoRepository.kt create mode 100644 src/main/kotlin/com/sangdol/demo/DemoService.kt create mode 100644 src/main/resources/application-deploy.yml create mode 100644 src/main/resources/application-local.yml delete mode 100644 src/test/kotlin/com/sangdol/demo/DemoApplicationTests.kt create mode 100644 src/test/kotlin/com/sangdol/demo/DemoServiceTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 40bdc33..6fce8c4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,55 +1,59 @@ plugins { - kotlin("jvm") version "1.9.25" - kotlin("plugin.spring") version "1.9.25" - id("org.springframework.boot") version "3.5.3" - id("io.spring.dependency-management") version "1.1.7" - kotlin("plugin.jpa") version "1.9.25" + kotlin("jvm") version "1.9.25" + kotlin("plugin.spring") version "1.9.25" + id("org.springframework.boot") version "3.5.3" + id("io.spring.dependency-management") version "1.1.7" + kotlin("plugin.jpa") version "1.9.25" } group = "com.sangdol" version = "0.0.1-SNAPSHOT" java { - toolchain { - languageVersion = JavaLanguageVersion.of(17) - } + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } } configurations { - compileOnly { - extendsFrom(configurations.annotationProcessor.get()) - } + compileOnly { + extendsFrom(configurations.annotationProcessor.get()) + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation("org.springframework.boot:spring-boot-starter-data-jpa") - implementation("org.springframework.boot:spring-boot-starter-web") - implementation("com.fasterxml.jackson.module:jackson-module-kotlin") - implementation("org.jetbrains.kotlin:kotlin-reflect") - runtimeOnly("com.h2database:h2") - runtimeOnly("com.mysql:mysql-connector-j") - annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") - testImplementation("org.springframework.boot:spring-boot-starter-test") - testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + implementation("org.jetbrains.kotlin:kotlin-reflect") + runtimeOnly("com.h2database:h2") + runtimeOnly("com.mysql:mysql-connector-j") + annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") + + + testImplementation("io.mockk:mockk:1.14.4") + testImplementation("io.kotest:kotest-runner-junit5:5.9.1") + testImplementation("org.springframework.boot:spring-boot-starter-test") + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") } kotlin { - compilerOptions { - freeCompilerArgs.addAll("-Xjsr305=strict") - } + compilerOptions { + freeCompilerArgs.addAll("-Xjsr305=strict") + } } allOpen { - annotation("jakarta.persistence.Entity") - annotation("jakarta.persistence.MappedSuperclass") - annotation("jakarta.persistence.Embeddable") + annotation("jakarta.persistence.Entity") + annotation("jakarta.persistence.MappedSuperclass") + annotation("jakarta.persistence.Embeddable") } tasks.withType { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/src/main/kotlin/com/sangdol/demo/DataSourceLookupController.kt b/src/main/kotlin/com/sangdol/demo/DataSourceLookupController.kt new file mode 100644 index 0000000..995bdab --- /dev/null +++ b/src/main/kotlin/com/sangdol/demo/DataSourceLookupController.kt @@ -0,0 +1,27 @@ +package com.sangdol.demo + +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RestController + + +@RestController +class DataSourceLookupController( + private val properties: DataSourceProperties, +) { + + @GetMapping("/dataSource") + fun lookup(): DataSourceInfoResponse = DataSourceInfoResponse( + properties.url, + properties.username, + properties.password, + properties.driverClassName + ) +} + +data class DataSourceInfoResponse( + val url: String, + val username: String, + val password: String, + val driverClassName: String, +) \ No newline at end of file diff --git a/src/main/kotlin/com/sangdol/demo/DemoController.kt b/src/main/kotlin/com/sangdol/demo/DemoController.kt new file mode 100644 index 0000000..14fbdf1 --- /dev/null +++ b/src/main/kotlin/com/sangdol/demo/DemoController.kt @@ -0,0 +1,52 @@ +package com.sangdol.demo + +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/demo") +class DemoController( + private val demoService: DemoService +) { + @GetMapping + fun findAll(): List = demoService.findAll() + + @GetMapping("/{id}") + fun findById( + @PathVariable id: Long + ): FindResponse = demoService.findById(id) + + @PostMapping + fun create( + @RequestBody request: CreateRequest + ): CreateResponse = demoService.create(request) +} + +data class CreateRequest( + val name: String, + val age: Int, +) + +fun CreateRequest.toEntity(): DemoEntity = DemoEntity( + name = this.name, + age = this.age +) + +data class CreateResponse( + val id: Long, +) + +fun DemoEntity.toCreateResponse(): CreateResponse = CreateResponse( + id = this.id ?: throw IllegalStateException("ID not generated"), +) + +data class FindResponse( + val id: Long, + val name: String, + val age: Int +) + +fun DemoEntity.toFindResponse(): FindResponse = FindResponse( + id = this.id ?: throw IllegalStateException("ID not generated"), + name = this.name, + age = this.age +) diff --git a/src/main/kotlin/com/sangdol/demo/DemoEntity.kt b/src/main/kotlin/com/sangdol/demo/DemoEntity.kt new file mode 100644 index 0000000..c2cafe1 --- /dev/null +++ b/src/main/kotlin/com/sangdol/demo/DemoEntity.kt @@ -0,0 +1,20 @@ +package com.sangdol.demo + +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.Table + +@Entity +@Table(name = "demo") +class DemoEntity( + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + var id: Long? = null, + + var name: String, + var age: Int, +) + diff --git a/src/main/kotlin/com/sangdol/demo/DemoRepository.kt b/src/main/kotlin/com/sangdol/demo/DemoRepository.kt new file mode 100644 index 0000000..35d2ec0 --- /dev/null +++ b/src/main/kotlin/com/sangdol/demo/DemoRepository.kt @@ -0,0 +1,6 @@ +package com.sangdol.demo + +import org.springframework.data.jpa.repository.JpaRepository + +interface DemoRepository : JpaRepository { +} \ No newline at end of file diff --git a/src/main/kotlin/com/sangdol/demo/DemoService.kt b/src/main/kotlin/com/sangdol/demo/DemoService.kt new file mode 100644 index 0000000..5c9d795 --- /dev/null +++ b/src/main/kotlin/com/sangdol/demo/DemoService.kt @@ -0,0 +1,26 @@ +package com.sangdol.demo + +import org.springframework.data.repository.findByIdOrNull +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional +import org.springframework.web.bind.annotation.RequestBody + +@Service +class DemoService( + private val demoRepository: DemoRepository +) { + @Transactional(readOnly = true) + fun findAll(): List = demoRepository.findAll() + .map { it.toFindResponse() } + + @Transactional(readOnly = true) + fun findById(id: Long): FindResponse = demoRepository.findByIdOrNull(id) + ?.toFindResponse() + ?: throw IllegalArgumentException("Entity with id: $id not exist") + + @Transactional + fun create( + @RequestBody request: CreateRequest + ): CreateResponse = demoRepository.save(request.toEntity()) + .toCreateResponse() +} diff --git a/src/main/resources/application-deploy.yml b/src/main/resources/application-deploy.yml new file mode 100644 index 0000000..4adb4a5 --- /dev/null +++ b/src/main/resources/application-deploy.yml @@ -0,0 +1,8 @@ +spring: + datasource: + driver-class-name: ${SPRING_DATASOURCE_DRIVER_CLASS_NAME} + url: ${SPRING_DATASOURCE_URL} + username: ${SPRING_DATASOURCE_USERNAME} + password: ${SPRING_DATASOURCE_PASSWORD} + + diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml new file mode 100644 index 0000000..ad26f8a --- /dev/null +++ b/src/main/resources/application-local.yml @@ -0,0 +1,15 @@ +spring: + datasource: + url: jdbc:h2:mem:test + username: sa + password: + driver-class-name: org.h2.Driver + jpa: + database-platform: org.hibernate.dialect.H2Dialect + show-sql: true + properties: + hibernate: + format_sql: true + h2: + console: + enabled: true diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6deaa91..8c5abe8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,12 @@ spring: application: name: argo-vault-demo + + profiles: + active: ${SPRING_PROFILE:local} + + jpa: + hibernate: + ddl-auto: none + defer-datasource-initialization: true + open-in-view: false \ No newline at end of file diff --git a/src/test/kotlin/com/sangdol/demo/DemoApplicationTests.kt b/src/test/kotlin/com/sangdol/demo/DemoApplicationTests.kt deleted file mode 100644 index dcd7f01..0000000 --- a/src/test/kotlin/com/sangdol/demo/DemoApplicationTests.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.sangdol.demo - -import org.junit.jupiter.api.Test -import org.springframework.boot.test.context.SpringBootTest - -@SpringBootTest -class DemoApplicationTests { - - @Test - fun contextLoads() { - } - -} diff --git a/src/test/kotlin/com/sangdol/demo/DemoServiceTest.kt b/src/test/kotlin/com/sangdol/demo/DemoServiceTest.kt new file mode 100644 index 0000000..4ebc03c --- /dev/null +++ b/src/test/kotlin/com/sangdol/demo/DemoServiceTest.kt @@ -0,0 +1,43 @@ +package com.sangdol.demo + +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import io.mockk.every +import io.mockk.mockk +import org.springframework.data.repository.findByIdOrNull + +internal class DemoServiceTest : BehaviorSpec({ + + val demoRepository: DemoRepository = mockk() + val demoService = DemoService(demoRepository) + + Given("Entity를 ID로 조회할 때") { + val id: Long = 1L + + When("입력된 ID로 조회되는 결과가 없다면") { + every { demoRepository.findByIdOrNull(id) } returns null + + Then("IllegalArgumentException이 발생한다.") { + shouldThrow { demoService.findById(id) } + } + } + + When("입력된 ID로 조회되는 결과가 있다면") { + every { demoRepository.findByIdOrNull(id) } returns DemoEntity(id, "hello kotlin", 20) + + Then("정상적으로 반환된다.") { + val result: FindResponse = demoService.findById(id) + result shouldNotBe null + + assertSoftly(result) { + this.id shouldBe id + this.name shouldBe "hello kotlin" + this.age shouldBe 20 + } + } + } + } +})