@@ -2,67 +2,136 @@ package com.back.koreaTravelGuide.domain.ai.tour.client
22
33import com.back.koreaTravelGuide.KoreaTravelGuideApplication
44import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams
5- import org.junit.jupiter.api.Assumptions.assumeTrue
5+ import com.fasterxml.jackson.databind.ObjectMapper
6+ import org.junit.jupiter.api.AfterEach
7+ import org.junit.jupiter.api.BeforeEach
68import org.junit.jupiter.api.DisplayName
9+ import org.junit.jupiter.api.Nested
710import org.junit.jupiter.api.Test
8- import org.springframework.beans.factory.annotation.Autowired
9- import org.springframework.beans.factory.annotation.Value
1011import org.springframework.boot.test.context.SpringBootTest
12+ import org.springframework.http.HttpMethod
13+ import org.springframework.http.HttpStatus
14+ import org.springframework.http.MediaType
1115import org.springframework.test.context.ActiveProfiles
16+ import org.springframework.test.web.client.ExpectedCount
17+ import org.springframework.test.web.client.MockRestServiceServer
18+ import org.springframework.test.web.client.match.MockRestRequestMatchers.method
19+ import org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo
20+ import org.springframework.test.web.client.response.MockRestResponseCreators.withStatus
21+ import org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess
22+ import org.springframework.web.client.RestTemplate
23+ import org.springframework.web.util.UriComponentsBuilder
24+ import kotlin.test.assertEquals
1225import kotlin.test.assertTrue
1326
14- /* *
15- * 실제 관광청 API 상태를 확인하기 위한 통합 테스트.
16- */
1727@SpringBootTest(classes = [KoreaTravelGuideApplication ::class ])
1828@ActiveProfiles(" test" )
1929class TourApiClientTest {
20- @Autowired
21- private lateinit var tourApiClient: TourApiClient
30+ // MockRestServiceServer 기반 단위 테스트
31+ @Nested
32+ inner class MockServerTests {
33+ private lateinit var restTemplate: RestTemplate
34+ private lateinit var mockServer: MockRestServiceServer
35+ private lateinit var mockClient: TourApiClient
2236
23- @Value( " \$ {tour.api. key} " )
24- private lateinit var serviceKey : String
37+ private val serviceKey = " test-service- key"
38+ private val baseUrl = " https://example.com "
2539
26- @DisplayName(" fetchTourInfo - 실제 관광청 API 호출 (데이터 기대)" )
27- @Test
28- fun fetchTourInfoTest () {
29- val params =
30- TourParams (
31- contentTypeId = " 12" ,
32- areaCode = " 1" ,
33- sigunguCode = " 1" ,
34- )
40+ @BeforeEach
41+ fun setUp () {
42+ restTemplate = RestTemplate ()
43+ mockServer = MockRestServiceServer .createServer(restTemplate)
44+ mockClient = TourApiClient (restTemplate, ObjectMapper (), serviceKey, baseUrl)
45+ }
46+
47+ @AfterEach
48+ fun tearDown () {
49+ mockServer.verify()
50+ }
3551
36- val result = tourApiClient.fetchTourInfo(params)
52+ @DisplayName(" fetchTourInfo - 외부 API가 정상 응답을 반환하면 파싱된 결과를 제공" )
53+ @Test
54+ fun fetchTourInfoReturnsParsedItems () {
55+ val params =
56+ TourParams (
57+ contentTypeId = " 12" ,
58+ areaCode = " 1" ,
59+ sigunguCode = " 1" ,
60+ )
3761
38- println (" 실제 API 응답 아이템 수: ${result.items.size} " )
39- println (" 첫 번째 아이템: ${result.items.firstOrNull()} " )
62+ mockServer.expect(ExpectedCount .once(), requestTo(expectedAreaBasedListUrl(params)))
63+ .andExpect(method(HttpMethod .GET ))
64+ .andRespond(withSuccess(SUCCESS_RESPONSE , MediaType .APPLICATION_JSON ))
4065
41- assertTrue(result.items.isNotEmpty(), " 실제 API 호출 결과가 비어 있습니다. 장애 여부를 확인하세요." )
42- }
66+ val result = mockClient.fetchTourInfo(params)
4367
44- @DisplayName(" fetchTourInfo - 실제 관광청 API 장애 시 빈 결과 확인" )
45- @Test
46- fun fetchTourInfoEmptyTest () {
47- val params =
48- TourParams (
49- contentTypeId = " 12" ,
50- areaCode = " 1" ,
51- sigunguCode = " 1" ,
52- )
68+ assertEquals(1 , result.items.size)
69+ val firstItem = result.items.first()
70+ assertEquals(" 12345" , firstItem.contentId)
71+ assertEquals(" 테스트 타이틀" , firstItem.title)
72+ }
73+
74+ @DisplayName(" fetchTourInfo - 외부 API가 404를 반환하면 빈 결과를 전달" )
75+ @Test
76+ fun fetchTourInfoReturnsEmptyListWhenApiFails () {
77+ val params =
78+ TourParams (
79+ contentTypeId = " 12" ,
80+ areaCode = " 1" ,
81+ sigunguCode = " 1" ,
82+ )
5383
54- val result = tourApiClient.fetchTourInfo(params)
84+ mockServer.expect(ExpectedCount .once(), requestTo(expectedAreaBasedListUrl(params)))
85+ .andExpect(method(HttpMethod .GET ))
86+ .andRespond(withStatus(HttpStatus .NOT_FOUND ))
5587
56- println (" 실제 API 응답 아이템 수: ${result.items.size} " )
57- println (" 첫 번째 아이템: ${result.items.firstOrNull()} " )
88+ val result = mockClient.fetchTourInfo(params)
5889
59- // 장애가 아닐 경우, 테스트를 스킵
60- assumeTrue(result.items.isEmpty()) {
61- " API가 정상 응답을 반환하고 있어 장애 시나리오 테스트를 건너뜁니다."
90+ assertTrue(result.items.isEmpty())
6291 }
6392
64- // 장애 상황일 시
65- println (" 실제 API가 비어 있는 응답을 반환했습니다." )
66- assertTrue(result.items.isEmpty())
93+ private fun expectedAreaBasedListUrl (params : TourParams ): String =
94+ UriComponentsBuilder .fromUriString(baseUrl)
95+ .path(" /areaBasedList2" )
96+ .queryParam(" serviceKey" , serviceKey)
97+ .queryParam(" MobileOS" , " WEB" )
98+ .queryParam(" MobileApp" , " KoreaTravelGuide" )
99+ .queryParam(" _type" , " json" )
100+ .queryParam(" contentTypeId" , params.contentTypeId)
101+ .queryParam(" areaCode" , params.areaCode)
102+ .queryParam(" sigunguCode" , params.sigunguCode)
103+ .build()
104+ .encode()
105+ .toUriString()
106+ }
107+
108+ companion object {
109+ private val SUCCESS_RESPONSE =
110+ """
111+ {
112+ "response": {
113+ "header": {
114+ "resultCode": "0000",
115+ "resultMsg": "OK"
116+ },
117+ "body": {
118+ "items": {
119+ "item": [
120+ {
121+ "contentid": "12345",
122+ "contenttypeid": "12",
123+ "createdtime": "202310010000",
124+ "modifiedtime": "202310020000",
125+ "title": "테스트 타이틀",
126+ "addr1": "서울특별시 종로구",
127+ "areacode": "1",
128+ "firstimage": "https://example.com/image.jpg"
129+ }
130+ ]
131+ }
132+ }
133+ }
134+ }
135+ """ .trimIndent()
67136 }
68137}
0 commit comments