@@ -4,12 +4,10 @@ import com.back.koreaTravelGuide.KoreaTravelGuideApplication
44import com.back.koreaTravelGuide.domain.ai.tour.dto.TourParams
55import com.fasterxml.jackson.databind.ObjectMapper
66import org.junit.jupiter.api.AfterEach
7- import org.junit.jupiter.api.Assumptions.assumeTrue
87import org.junit.jupiter.api.BeforeEach
98import org.junit.jupiter.api.DisplayName
109import org.junit.jupiter.api.Nested
1110import org.junit.jupiter.api.Test
12- import org.springframework.beans.factory.annotation.Autowired
1311import org.springframework.boot.test.context.SpringBootTest
1412import org.springframework.http.HttpMethod
1513import org.springframework.http.HttpStatus
@@ -26,139 +24,114 @@ import org.springframework.web.util.UriComponentsBuilder
2624import kotlin.test.assertEquals
2725import kotlin.test.assertTrue
2826
29- // 실제 API 호출 기반 단위 테스트
3027@SpringBootTest(classes = [KoreaTravelGuideApplication ::class ])
3128@ActiveProfiles(" test" )
32- class TourApiClientTest
33- @Autowired
34- constructor (
35- private val tourApiClient: TourApiClient ,
36- ) {
37- @DisplayName(" fetchTourInfo - 실제 관광청 API가 빈 응답을 줄 경우" )
29+ class TourApiClientTest {
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
36+
37+ private val serviceKey = " test-service-key"
38+ private val baseUrl = " https://example.com"
39+
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+ }
51+
52+ @DisplayName(" fetchTourInfo - 외부 API가 정상 응답을 반환하면 파싱된 결과를 제공" )
3853 @Test
39- fun fetchTourInfoRealCallEmptyResponse () {
54+ fun fetchTourInfoReturnsParsedItems () {
4055 val params =
4156 TourParams (
4257 contentTypeId = " 12" ,
4358 areaCode = " 1" ,
4459 sigunguCode = " 1" ,
4560 )
4661
47- val result = tourApiClient.fetchTourInfo(params)
62+ mockServer.expect(ExpectedCount .once(), requestTo(expectedAreaBasedListUrl(params)))
63+ .andExpect(method(HttpMethod .GET ))
64+ .andRespond(withSuccess(SUCCESS_RESPONSE , MediaType .APPLICATION_JSON ))
4865
49- // isEmpty가 true인 경우 테스트를 진행, 아닐 경우 메세지 출력
50- assumeTrue(result.items.isEmpty()) {
51- " 관광청 API가 정상 데이터를 제공하고 있어 장애 시나리오 테스트를 건너뜁니다."
52- }
66+ val result = mockClient.fetchTourInfo(params)
5367
54- assertTrue(result.items.isEmpty())
68+ assertEquals(1 , result.items.size)
69+ val firstItem = result.items.first()
70+ assertEquals(" 12345" , firstItem.contentId)
71+ assertEquals(" 테스트 타이틀" , firstItem.title)
5572 }
5673
57- // MockRestServiceServer 기반 단위 테스트
58- @Nested
59- inner class MockServerTests {
60- private lateinit var restTemplate: RestTemplate
61- private lateinit var mockServer: MockRestServiceServer
62- private lateinit var mockClient: TourApiClient
63-
64- private val serviceKey = " test-service-key"
65- private val baseUrl = " https://example.com"
66-
67- @BeforeEach
68- fun setUp () {
69- restTemplate = RestTemplate ()
70- mockServer = MockRestServiceServer .createServer(restTemplate)
71- mockClient = TourApiClient (restTemplate, ObjectMapper (), serviceKey, baseUrl)
72- }
73-
74- @AfterEach
75- fun tearDown () {
76- mockServer.verify()
77- }
78-
79- @DisplayName(" fetchTourInfo - 외부 API가 정상 응답을 반환하면 파싱된 결과를 제공" )
80- @Test
81- fun fetchTourInfoReturnsParsedItems () {
82- val params =
83- TourParams (
84- contentTypeId = " 12" ,
85- areaCode = " 1" ,
86- sigunguCode = " 1" ,
87- )
88-
89- mockServer.expect(ExpectedCount .once(), requestTo(expectedAreaBasedListUrl(params)))
90- .andExpect(method(HttpMethod .GET ))
91- .andRespond(withSuccess(SUCCESS_RESPONSE , MediaType .APPLICATION_JSON ))
92-
93- val result = mockClient.fetchTourInfo(params)
94-
95- assertEquals(1 , result.items.size)
96- val firstItem = result.items.first()
97- assertEquals(" 12345" , firstItem.contentId)
98- assertEquals(" 테스트 타이틀" , firstItem.title)
99- }
100-
101- @DisplayName(" fetchTourInfo - 외부 API가 404를 반환하면 빈 결과를 전달" )
102- @Test
103- fun fetchTourInfoReturnsEmptyListWhenApiFails () {
104- val params =
105- TourParams (
106- contentTypeId = " 12" ,
107- areaCode = " 1" ,
108- sigunguCode = " 1" ,
109- )
110-
111- mockServer.expect(ExpectedCount .once(), requestTo(expectedAreaBasedListUrl(params)))
112- .andExpect(method(HttpMethod .GET ))
113- .andRespond(withStatus(HttpStatus .NOT_FOUND ))
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+ )
11483
115- val result = mockClient.fetchTourInfo(params)
84+ mockServer.expect(ExpectedCount .once(), requestTo(expectedAreaBasedListUrl(params)))
85+ .andExpect(method(HttpMethod .GET ))
86+ .andRespond(withStatus(HttpStatus .NOT_FOUND ))
11687
117- assertTrue(result.items.isEmpty())
118- }
88+ val result = mockClient.fetchTourInfo(params)
11989
120- private fun expectedAreaBasedListUrl (params : TourParams ): String =
121- UriComponentsBuilder .fromUriString(baseUrl)
122- .path(" /areaBasedList2" )
123- .queryParam(" serviceKey" , serviceKey)
124- .queryParam(" MobileOS" , " WEB" )
125- .queryParam(" MobileApp" , " KoreaTravelGuide" )
126- .queryParam(" _type" , " json" )
127- .queryParam(" contentTypeId" , params.contentTypeId)
128- .queryParam(" areaCode" , params.areaCode)
129- .queryParam(" sigunguCode" , params.sigunguCode)
130- .build()
131- .encode()
132- .toUriString()
90+ assertTrue(result.items.isEmpty())
13391 }
13492
135- companion object {
136- private val SUCCESS_RESPONSE =
137- """
138- {
139- "response": {
140- "header": {
141- "resultCode": "0000",
142- "resultMsg": "OK"
143- },
144- "body": {
145- "items": {
146- "item": [
147- {
148- "contentid": "12345",
149- "contenttypeid": "12",
150- "createdtime": "202310010000",
151- "modifiedtime": "202310020000",
152- "title": "테스트 타이틀",
153- "addr1": "서울특별시 종로구",
154- "areacode": "1",
155- "firstimage": "https://example.com/image.jpg"
156- }
157- ]
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"
158129 }
159- }
130+ ]
160131 }
161132 }
162- """ .trimIndent()
163- }
133+ }
134+ }
135+ """ .trimIndent()
164136 }
137+ }
0 commit comments