feat: implement application for testing

This commit is contained in:
이상진 2025-07-10 12:17:52 +09:00
parent 79dc26877e
commit 29a8e834b9
11 changed files with 239 additions and 42 deletions

View File

@ -1,55 +1,59 @@
plugins { plugins {
kotlin("jvm") version "1.9.25" kotlin("jvm") version "1.9.25"
kotlin("plugin.spring") version "1.9.25" kotlin("plugin.spring") version "1.9.25"
id("org.springframework.boot") version "3.5.3" id("org.springframework.boot") version "3.5.3"
id("io.spring.dependency-management") version "1.1.7" id("io.spring.dependency-management") version "1.1.7"
kotlin("plugin.jpa") version "1.9.25" kotlin("plugin.jpa") version "1.9.25"
} }
group = "com.sangdol" group = "com.sangdol"
version = "0.0.1-SNAPSHOT" version = "0.0.1-SNAPSHOT"
java { java {
toolchain { toolchain {
languageVersion = JavaLanguageVersion.of(17) languageVersion = JavaLanguageVersion.of(17)
} }
} }
configurations { configurations {
compileOnly { compileOnly {
extendsFrom(configurations.annotationProcessor.get()) extendsFrom(configurations.annotationProcessor.get())
} }
} }
repositories { repositories {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-reflect")
runtimeOnly("com.h2database:h2") runtimeOnly("com.h2database:h2")
runtimeOnly("com.mysql:mysql-connector-j") runtimeOnly("com.mysql:mysql-connector-j")
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") 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") 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 { kotlin {
compilerOptions { compilerOptions {
freeCompilerArgs.addAll("-Xjsr305=strict") freeCompilerArgs.addAll("-Xjsr305=strict")
} }
} }
allOpen { allOpen {
annotation("jakarta.persistence.Entity") annotation("jakarta.persistence.Entity")
annotation("jakarta.persistence.MappedSuperclass") annotation("jakarta.persistence.MappedSuperclass")
annotation("jakarta.persistence.Embeddable") annotation("jakarta.persistence.Embeddable")
} }
tasks.withType<Test> { tasks.withType<Test> {
useJUnitPlatform() useJUnitPlatform()
} }

View File

@ -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,
)

View File

@ -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<FindResponse> = 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
)

View File

@ -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,
)

View File

@ -0,0 +1,6 @@
package com.sangdol.demo
import org.springframework.data.jpa.repository.JpaRepository
interface DemoRepository : JpaRepository<DemoEntity, Long> {
}

View File

@ -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<FindResponse> = 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()
}

View File

@ -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}

View File

@ -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

View File

@ -1,3 +1,12 @@
spring: spring:
application: application:
name: argo-vault-demo name: argo-vault-demo
profiles:
active: ${SPRING_PROFILE:local}
jpa:
hibernate:
ddl-auto: none
defer-datasource-initialization: true
open-in-view: false

View File

@ -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() {
}
}

View File

@ -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<IllegalArgumentException> { 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
}
}
}
}
})