Skip to content

Commit cbfefb4

Browse files
[REFACTOR] controller 슬라이스 테스트 추가, mapper 계층 추가 (#62)
* [REFACTOR] 컨트롤러 응답 매핑 로직 전담 매퍼로 분리 * [REFACTOR] controller 통합 테스트 추가, mapper 계층 추가 * [TEST] 카페 도메인 생성 테스트 코드 추가 * [TEST] 커피빈 도메인 테스트 코드 추가 * [TEST] 원두 산지 도메인 테스트 코드 추가 * [TEST] 메뉴 테스트 코드 작성 * [TEST] Tag 도메인 테스트 코드 추가 * [CHORE] gradle.kts 형식 수정
1 parent 001c2cb commit cbfefb4

18 files changed

Lines changed: 620 additions & 75 deletions

File tree

build.gradle.kts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import org.asciidoctor.gradle.jvm.AsciidoctorTask
2+
13
plugins {
24
kotlin("jvm") version Plugin.KOTLIN_JVM.version
35
kotlin("plugin.spring") version Plugin.KOTLIN_SPRING.version
@@ -9,6 +11,7 @@ plugins {
911
id(Plugin.OPENAPI.id) version Plugin.OPENAPI.version
1012
id(Plugin.ECLIPSE_APT.id) version Plugin.ECLIPSE_APT.version
1113
id(Plugin.KTLINT.id) version Plugin.KTLINT.version
14+
id(Plugin.ASCIIDOCTOR.id) version Plugin.ASCIIDOCTOR.version
1215
}
1316

1417
allOpen {
@@ -25,6 +28,14 @@ java {
2528
}
2629
}
2730

31+
configurations {
32+
getByName("compileOnly") {
33+
extendsFrom(configurations["annotationProcessor"])
34+
}
35+
36+
create("asciidoctorExt")
37+
}
38+
2839
repositories {
2940
mavenCentral()
3041
}
@@ -46,6 +57,10 @@ dependencies {
4657
testRuntimeOnly(Dependency.Test.JUNIT_PLATFORM)
4758
testImplementation(Dependency.Test.MOCKK)
4859

60+
// Kotest
61+
testImplementation(Dependency.Test.KOTEST_RUNNER)
62+
testImplementation(Dependency.Test.KOTEST_ASSERTIONS_CORE)
63+
4964
// Database
5065
runtimeOnly(Dependency.Database.MYSQL_CONNECTOR)
5166

@@ -54,6 +69,8 @@ dependencies {
5469

5570
// Docs
5671
implementation(Dependency.Spring.SPRINGDOC)
72+
add("asciidoctorExt", "org.springframework.restdocs:spring-restdocs-asciidoctor")
73+
testImplementation(Dependency.Spring.RESTDOCS_MOCKMVC)
5774

5875
// JDSL
5976
implementation(Dependency.JDSL.JPQL_DSL)
@@ -70,10 +87,33 @@ kotlin {
7087
}
7188
}
7289

73-
tasks.withType<Test> {
90+
tasks.withType<Test>().configureEach {
7491
useJUnitPlatform()
7592
}
7693

7794
tasks.withType<org.jmailen.gradle.kotlinter.tasks.LintTask> {
7895
enabled = false
7996
}
97+
98+
val snippetsDir = file("build/generated-snippets")
99+
100+
tasks.test {
101+
outputs.dir(snippetsDir)
102+
}
103+
104+
tasks.named<AsciidoctorTask>("asciidoctor") {
105+
inputs.dir(snippetsDir)
106+
configurations("asciidoctorExt")
107+
sources {
108+
include("**/index.adoc")
109+
}
110+
baseDirFollowsSourceFile()
111+
dependsOn(tasks.test)
112+
}
113+
114+
tasks.bootJar {
115+
dependsOn(tasks.named<AsciidoctorTask>("asciidoctor"))
116+
from(tasks.named<AsciidoctorTask>("asciidoctor").get().outputDir) {
117+
into("static/docs")
118+
}
119+
}

buildSrc/src/main/kotlin/Dependency.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ object Dependency {
1313
val BOOT_STARTER_JPA = starter("-data-jpa")
1414
val BOOT_STARTER_ACTUATOR = starter("-actuator")
1515
val SPRINGDOC = "org.springdoc:springdoc-openapi-starter-webmvc-ui:$SPRINGDOC_VERSION"
16+
val RESTDOCS_MOCKMVC = "org.springframework.restdocs:spring-restdocs-mockmvc"
1617
}
1718

1819
object Kotlin {
@@ -28,12 +29,16 @@ object Dependency {
2829
object Test {
2930
private const val JUNIT_VERSION = "1.10.2"
3031
private const val MOCKK_VERSION = "1.13.9"
32+
private const val KOTEST_RUNNER_VERSION = "5.8.0"
33+
private const val KOTEST_ASSERTIONS_CORE_VERSION = "5.9.0"
3134

3235
private const val BASE = "org.junit.platform"
3336
fun junit(name: String) = "$BASE:$name:$JUNIT_VERSION"
3437

3538
val JUNIT_PLATFORM = junit("junit-platform-launcher")
3639
val MOCKK = "io.mockk:mockk:$MOCKK_VERSION"
40+
val KOTEST_RUNNER = "io.kotest:kotest-runner-junit5:$KOTEST_RUNNER_VERSION"
41+
val KOTEST_ASSERTIONS_CORE = "io.kotest:kotest-assertions-core:$KOTEST_ASSERTIONS_CORE_VERSION"
3742
}
3843

3944
object Database {

buildSrc/src/main/kotlin/Plugin.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,26 @@ enum class Plugin(
22
val id: String,
33
val version: String
44
) {
5+
// Kotlin
56
KOTLIN_JVM("org.jetbrains.kotlin.jvm", "1.9.25"),
67
KOTLIN_SPRING("org.jetbrains.kotlin.plugin.spring", "1.9.25"),
78
KOTLIN_JPA("org.jetbrains.kotlin.plugin.jpa", "1.9.25"),
89
KOTLIN_ALLOPEN("org.jetbrains.kotlin.plugin.allopen", "1.9.25"),
910
KOTLIN_NOARG("org.jetbrains.kotlin.plugin.noarg", "1.9.25"),
11+
12+
// Lint
1013
KTLINT("org.jmailen.kotlinter", "3.16.0"),
1114

15+
// Spring
1216
SPRING_BOOT("org.springframework.boot", "3.4.1"),
1317
SPRING_DEPENDENCY_MANAGEMENT("io.spring.dependency-management", "1.1.7"),
1418

19+
// OpenAPI
1520
OPENAPI("org.openapi.generator", "6.5.0"),
16-
ECLIPSE_APT("com.diffplug.eclipse.apt", "3.26.0");
21+
22+
// Eclipse APT
23+
ECLIPSE_APT("com.diffplug.eclipse.apt", "3.26.0"),
24+
25+
// Asciidoctor
26+
ASCIIDOCTOR("org.asciidoctor.jvm.convert", "3.3.2");
1727
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// [[cafe-]]
2+
=== 카페 조회
3+
4+
=== HTTP Request
5+
6+
=== HTTP Response

src/docs/asciidoc/index.adoc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
ifndef::snippets[]
2+
:snippets: ../../build/generated-snippets
3+
endif::[]
4+
= Coffee REST API 문서
5+
:doctype: book
6+
:icons: font
7+
:source-highlighter: highlightjs
8+
:toc:left
9+
:toclevels: 2
10+
:sectlinks:
11+
12+
[[Cafe-API]]
13+
== Cafe API
14+
15+
include::api/cafe/cafe.adoc[]

src/main/kotlin/com/coffee/api/cafe/presentation/adapter/in/restapi/CafeController.kt

Lines changed: 4 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import com.coffee.api.cafe.application.port.inbound.FindCafeArea
55
import com.coffee.api.cafe.application.port.inbound.FindCafeDetails
66
import com.coffee.api.cafe.application.port.inbound.FindRecommendCafe
77
import com.coffee.api.cafe.presentation.adapter.`in`.restapi.dto.response.*
8+
import com.coffee.api.cafe.presentation.adapter.`in`.restapi.mapper.FindAllCafesResponseMapper
9+
import com.coffee.api.cafe.presentation.adapter.`in`.restapi.mapper.GetCafeDetailsResponseMapper
810
import com.coffee.api.cafe.presentation.docs.CafeApi
911
import com.coffee.api.common.support.response.ApiResponse
1012
import org.springframework.web.bind.annotation.GetMapping
@@ -29,29 +31,7 @@ class CafeController(
2931
@RequestParam(value = "area", required = false) area: String?,
3032
): ApiResponse<FindAllCafesResponseWrapper> {
3133
val result = findCafe.invoke(FindCafe.Query(lastCafeId, area))
32-
33-
val cafes = result.cafes.map { cafe ->
34-
FindAllCafesResponse(
35-
cafeId = cafe.cafeId.value.toString(),
36-
name = cafe.name,
37-
nearestStation = cafe.nearestStation,
38-
location = cafe.location,
39-
price = cafe.price,
40-
previewImages = cafe.previewImages,
41-
tags = cafe.tags.map { tag ->
42-
TagResponse(
43-
id = tag.id.value.toString(),
44-
name = tag.name,
45-
)
46-
},
47-
)
48-
}
49-
50-
val response = FindAllCafesResponseWrapper(
51-
cafes = cafes,
52-
hasNext = result.hasNext,
53-
)
54-
34+
val response = FindAllCafesResponseMapper.toResponse(result)
5535
return ApiResponse.success(response)
5636
}
5737

@@ -60,51 +40,7 @@ class CafeController(
6040
@PathVariable cafeId: UUID,
6141
): ApiResponse<GetCafeDetailsResponse> {
6242
val result = findCafeDetails.invoke(FindCafeDetails.Query(cafeId))
63-
64-
val response = GetCafeDetailsResponse(
65-
cafe = CafeResponse(
66-
id = result.cafeDetails.cafe.id.value.toString(),
67-
reasonForSelection = result.cafeDetails.cafe.reasonForSelection,
68-
naverMapUrl = result.cafeDetails.cafe.naverMapUrl,
69-
name = result.cafeDetails.cafe.name,
70-
nearestStation = result.cafeDetails.cafe.nearestStation,
71-
location = result.cafeDetails.cafe.location,
72-
price = result.cafeDetails.cafe.price,
73-
mainImageUrl = result.cafeDetails.cafe.mainImages,
74-
),
75-
coffeeBean = CoffeeBeanResponse(
76-
id = result.cafeDetails.coffeeBean.id.value.toString(),
77-
description = result.cafeDetails.coffeeBean.description,
78-
name = result.cafeDetails.coffeeBean.name,
79-
engName = result.cafeDetails.coffeeBean.engName,
80-
flavors = result.cafeDetails.coffeeBean.flavors.map { flavor ->
81-
FlavorResponse(
82-
flavor.displayName,
83-
flavor.category
84-
)
85-
},
86-
countryOfOrigin = result.cafeDetails.coffeeBean.countryOfOrigin,
87-
roastingPoint = result.cafeDetails.coffeeBean.roastingPoint.toString(),
88-
),
89-
menus = result.cafeDetails.menu.map { menu ->
90-
MenuResponse(
91-
id = menu.id.value.toString(),
92-
name = menu.name,
93-
price = menu.price,
94-
imageUrl = menu.imageUrl,
95-
description = menu.description,
96-
)
97-
},
98-
tags = result.cafeDetails.tag.map { tag ->
99-
DetailTagResponse(
100-
id = tag.id.value.toString(),
101-
name = tag.name,
102-
imageUrl = tag.imageUrl
103-
)
104-
},
105-
updatedAt = result.cafeDetails.updatedAt,
106-
)
107-
43+
val response = GetCafeDetailsResponseMapper.toResponse(result)
10844
return ApiResponse.success(response)
10945
}
11046

src/main/kotlin/com/coffee/api/cafe/presentation/adapter/in/restapi/dto/response/FindAllCafesResponse.kt

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
package com.coffee.api.cafe.presentation.adapter.`in`.restapi.dto.response
22

33

4-
data class FindAllCafesResponseWrapper(
5-
val cafes: List<FindAllCafesResponse>,
6-
val hasNext: Boolean
7-
)
8-
94
data class FindAllCafesResponse(
105
val cafeId: String,
116
val name: String,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.coffee.api.cafe.presentation.adapter.`in`.restapi.dto.response
2+
3+
data class FindAllCafesResponseWrapper(
4+
val cafes: List<FindAllCafesResponse>,
5+
val hasNext: Boolean
6+
)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.coffee.api.cafe.presentation.adapter.`in`.restapi.mapper
2+
3+
import com.coffee.api.cafe.application.port.inbound.FindCafe
4+
import com.coffee.api.cafe.presentation.adapter.`in`.restapi.dto.response.FindAllCafesResponse
5+
import com.coffee.api.cafe.presentation.adapter.`in`.restapi.dto.response.FindAllCafesResponseWrapper
6+
import com.coffee.api.cafe.presentation.adapter.`in`.restapi.dto.response.TagResponse
7+
8+
object FindAllCafesResponseMapper {
9+
fun toResponse(result: FindCafe.Result): FindAllCafesResponseWrapper {
10+
val cafes = result.cafes.map { cafe ->
11+
FindAllCafesResponse(
12+
cafeId = cafe.cafeId.value.toString(),
13+
name = cafe.name,
14+
nearestStation = cafe.nearestStation,
15+
location = cafe.location,
16+
price = cafe.price,
17+
previewImages = cafe.previewImages,
18+
tags = cafe.tags.map { tag ->
19+
TagResponse(
20+
id = tag.id.value.toString(),
21+
name = tag.name,
22+
)
23+
}
24+
)
25+
}
26+
return FindAllCafesResponseWrapper(
27+
cafes = cafes,
28+
hasNext = result.hasNext,
29+
)
30+
}
31+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.coffee.api.cafe.presentation.adapter.`in`.restapi.mapper
2+
3+
import com.coffee.api.cafe.application.port.inbound.FindCafeDetails
4+
import com.coffee.api.cafe.presentation.adapter.`in`.restapi.dto.response.*
5+
6+
object GetCafeDetailsResponseMapper {
7+
fun toResponse(result: FindCafeDetails.Result): GetCafeDetailsResponse {
8+
return GetCafeDetailsResponse(
9+
cafe = CafeResponse(
10+
id = result.cafeDetails.cafe.id.value.toString(),
11+
reasonForSelection = result.cafeDetails.cafe.reasonForSelection,
12+
naverMapUrl = result.cafeDetails.cafe.naverMapUrl,
13+
name = result.cafeDetails.cafe.name,
14+
nearestStation = result.cafeDetails.cafe.nearestStation,
15+
location = result.cafeDetails.cafe.location,
16+
price = result.cafeDetails.cafe.price,
17+
mainImageUrl = result.cafeDetails.cafe.mainImages,
18+
),
19+
coffeeBean = CoffeeBeanResponse(
20+
id = result.cafeDetails.coffeeBean.id.value.toString(),
21+
description = result.cafeDetails.coffeeBean.description,
22+
name = result.cafeDetails.coffeeBean.name,
23+
engName = result.cafeDetails.coffeeBean.engName,
24+
flavors = result.cafeDetails.coffeeBean.flavors.map { flavor ->
25+
FlavorResponse(
26+
flavor.displayName,
27+
flavor.category
28+
)
29+
},
30+
countryOfOrigin = result.cafeDetails.coffeeBean.countryOfOrigin,
31+
roastingPoint = result.cafeDetails.coffeeBean.roastingPoint.toString(),
32+
),
33+
menus = result.cafeDetails.menu.map { menu ->
34+
MenuResponse(
35+
id = menu.id.value.toString(),
36+
name = menu.name,
37+
price = menu.price,
38+
imageUrl = menu.imageUrl,
39+
description = menu.description,
40+
)
41+
},
42+
tags = result.cafeDetails.tag.map { tag ->
43+
DetailTagResponse(
44+
id = tag.id.value.toString(),
45+
name = tag.name,
46+
imageUrl = tag.imageUrl
47+
)
48+
},
49+
updatedAt = result.cafeDetails.updatedAt
50+
)
51+
}
52+
}

0 commit comments

Comments
 (0)