Skip to content

Commit d1a89f3

Browse files
committed
fix(be) : Test코드 통과하도록 수정, Mock 테스트 추가
1 parent 87e25d3 commit d1a89f3

File tree

1 file changed

+129
-34
lines changed

1 file changed

+129
-34
lines changed

src/test/kotlin/com/back/koreaTravelGuide/domain/ai/tour/client/TourApiClientTest.kt

Lines changed: 129 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,39 @@ package com.back.koreaTravelGuide.domain.ai.tour.client
22

33
import com.back.koreaTravelGuide.KoreaTravelGuideApplication
44
import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams
5+
import com.fasterxml.jackson.databind.ObjectMapper
6+
import org.junit.jupiter.api.AfterEach
57
import org.junit.jupiter.api.Assumptions.assumeTrue
8+
import org.junit.jupiter.api.BeforeEach
69
import org.junit.jupiter.api.DisplayName
10+
import org.junit.jupiter.api.Nested
711
import org.junit.jupiter.api.Test
812
import org.springframework.beans.factory.annotation.Autowired
9-
import org.springframework.beans.factory.annotation.Value
1013
import org.springframework.boot.test.context.SpringBootTest
14+
import org.springframework.http.HttpMethod
15+
import org.springframework.http.HttpStatus
16+
import org.springframework.http.MediaType
1117
import org.springframework.test.context.ActiveProfiles
18+
import org.springframework.test.web.client.ExpectedCount
19+
import org.springframework.test.web.client.MockRestServiceServer
20+
import org.springframework.test.web.client.match.MockRestRequestMatchers.method
21+
import org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo
22+
import org.springframework.test.web.client.response.MockRestResponseCreators.withStatus
23+
import org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess
24+
import org.springframework.web.client.RestTemplate
25+
import org.springframework.web.util.UriComponentsBuilder
26+
import kotlin.test.assertEquals
1227
import kotlin.test.assertTrue
1328

14-
/**
15-
* 실제 관광청 API 상태를 확인하기 위한 통합 테스트.
16-
*/
29+
// 실제 API 호출 기반 단위 테스트
1730
@SpringBootTest(classes = [KoreaTravelGuideApplication::class])
1831
@ActiveProfiles("test")
19-
class TourApiClientTest {
20-
@Autowired
21-
private lateinit var tourApiClient: TourApiClient
22-
23-
@Value("\${tour.api.key}")
24-
private lateinit var serviceKey: String
25-
26-
@DisplayName("fetchTourInfo - 실제 관광청 API 호출 (데이터 기대)")
32+
class TourApiClientTest @Autowired constructor(
33+
private val tourApiClient: TourApiClient,
34+
) {
35+
@DisplayName("fetchTourInfo - 실제 관광청 API가 빈 응답을 줄 경우")
2736
@Test
28-
fun fetchTourInfoTest() {
37+
fun fetchTourInfoRealCallEmptyResponse() {
2938
val params =
3039
TourParams(
3140
contentTypeId = "12",
@@ -35,34 +44,120 @@ class TourApiClientTest {
3544

3645
val result = tourApiClient.fetchTourInfo(params)
3746

38-
println("실제 API 응답 아이템 수: ${result.items.size}")
39-
println("첫 번째 아이템: ${result.items.firstOrNull()}")
47+
// isEmpty가 true인 경우 테스트를 진행, 아닐 경우 메세지 출력
48+
assumeTrue(result.items.isEmpty()) {
49+
"관광청 API가 정상 데이터를 제공하고 있어 장애 시나리오 테스트를 건너뜁니다."
50+
}
4051

41-
assertTrue(result.items.isNotEmpty(), "실제 API 호출 결과가 비어 있습니다. 장애 여부를 확인하세요.")
52+
assertTrue(result.items.isEmpty())
4253
}
4354

44-
@DisplayName("fetchTourInfo - 실제 관광청 API 장애 시 빈 결과 확인")
45-
@Test
46-
fun fetchTourInfoEmptyTest() {
47-
val params =
48-
TourParams(
49-
contentTypeId = "12",
50-
areaCode = "1",
51-
sigunguCode = "1",
52-
)
55+
// MockRestServiceServer 기반 단위 테스트
56+
@Nested
57+
inner class MockServerTests {
58+
private lateinit var restTemplate: RestTemplate
59+
private lateinit var mockServer: MockRestServiceServer
60+
private lateinit var mockClient: TourApiClient
5361

54-
val result = tourApiClient.fetchTourInfo(params)
62+
private val serviceKey = "test-service-key"
63+
private val baseUrl = "https://example.com"
5564

56-
println("실제 API 응답 아이템 수: ${result.items.size}")
57-
println("첫 번째 아이템: ${result.items.firstOrNull()}")
65+
@BeforeEach
66+
fun setUp() {
67+
restTemplate = RestTemplate()
68+
mockServer = MockRestServiceServer.createServer(restTemplate)
69+
mockClient = TourApiClient(restTemplate, ObjectMapper(), serviceKey, baseUrl)
70+
}
5871

59-
// 장애가 아닐 경우, 테스트를 스킵
60-
assumeTrue(result.items.isEmpty()) {
61-
"API가 정상 응답을 반환하고 있어 장애 시나리오 테스트를 건너뜁니다."
72+
@AfterEach
73+
fun tearDown() {
74+
mockServer.verify()
6275
}
6376

64-
// 장애 상황일 시
65-
println("실제 API가 비어 있는 응답을 반환했습니다.")
66-
assertTrue(result.items.isEmpty())
77+
@DisplayName("fetchTourInfo - 외부 API가 정상 응답을 반환하면 파싱된 결과를 제공")
78+
@Test
79+
fun fetchTourInfoReturnsParsedItems() {
80+
val params =
81+
TourParams(
82+
contentTypeId = "12",
83+
areaCode = "1",
84+
sigunguCode = "1",
85+
)
86+
87+
mockServer.expect(ExpectedCount.once(), requestTo(expectedAreaBasedListUrl(params)))
88+
.andExpect(method(HttpMethod.GET))
89+
.andRespond(withSuccess(SUCCESS_RESPONSE, MediaType.APPLICATION_JSON))
90+
91+
val result = mockClient.fetchTourInfo(params)
92+
93+
assertEquals(1, result.items.size)
94+
val firstItem = result.items.first()
95+
assertEquals("12345", firstItem.contentId)
96+
assertEquals("테스트 타이틀", firstItem.title)
97+
}
98+
99+
@DisplayName("fetchTourInfo - 외부 API가 404를 반환하면 빈 결과를 전달")
100+
@Test
101+
fun fetchTourInfoReturnsEmptyListWhenApiFails() {
102+
val params =
103+
TourParams(
104+
contentTypeId = "12",
105+
areaCode = "1",
106+
sigunguCode = "1",
107+
)
108+
109+
mockServer.expect(ExpectedCount.once(), requestTo(expectedAreaBasedListUrl(params)))
110+
.andExpect(method(HttpMethod.GET))
111+
.andRespond(withStatus(HttpStatus.NOT_FOUND))
112+
113+
val result = mockClient.fetchTourInfo(params)
114+
115+
assertTrue(result.items.isEmpty())
116+
}
117+
118+
private fun expectedAreaBasedListUrl(params: TourParams): String =
119+
UriComponentsBuilder.fromUriString(baseUrl)
120+
.path("/areaBasedList2")
121+
.queryParam("serviceKey", serviceKey)
122+
.queryParam("MobileOS", "WEB")
123+
.queryParam("MobileApp", "KoreaTravelGuide")
124+
.queryParam("_type", "json")
125+
.queryParam("contentTypeId", params.contentTypeId)
126+
.queryParam("areaCode", params.areaCode)
127+
.queryParam("sigunguCode", params.sigunguCode)
128+
.build()
129+
.encode()
130+
.toUriString()
131+
132+
}
133+
134+
companion object {
135+
private val SUCCESS_RESPONSE =
136+
"""
137+
{
138+
"response": {
139+
"header": {
140+
"resultCode": "0000",
141+
"resultMsg": "OK"
142+
},
143+
"body": {
144+
"items": {
145+
"item": [
146+
{
147+
"contentid": "12345",
148+
"contenttypeid": "12",
149+
"createdtime": "202310010000",
150+
"modifiedtime": "202310020000",
151+
"title": "테스트 타이틀",
152+
"addr1": "서울특별시 종로구",
153+
"areacode": "1",
154+
"firstimage": "https://example.com/image.jpg"
155+
}
156+
]
157+
}
158+
}
159+
}
160+
}
161+
""".trimIndent()
67162
}
68163
}

0 commit comments

Comments
 (0)