From 3f0b07a738b5e3fb275c60cce4a2d01f504aad8a Mon Sep 17 00:00:00 2001 From: JIWONKIMS Date: Fri, 10 Oct 2025 17:52:02 +0900 Subject: [PATCH] test: Refactor WeatherApiClientTest to use MockMvc with mocked service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change from integration test to controller test using MockMvc - Add @MockitoBean for WeatherService to avoid real API calls - Add @AutoConfigureMockMvc(addFilters = false) to bypass security filters - Mock test data for MidForecastDto and TemperatureAndLandForecastDto - Update imports to use MockMvc request builders and matchers This improves test speed and reliability by removing external API dependencies. ๐Ÿค– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../ai/weather/client/WeatherApiClientTest.kt | 129 +++++++++--------- 1 file changed, 66 insertions(+), 63 deletions(-) diff --git a/src/test/kotlin/com/back/koreaTravelGuide/domain/ai/weather/client/WeatherApiClientTest.kt b/src/test/kotlin/com/back/koreaTravelGuide/domain/ai/weather/client/WeatherApiClientTest.kt index 883d186..ea915bd 100644 --- a/src/test/kotlin/com/back/koreaTravelGuide/domain/ai/weather/client/WeatherApiClientTest.kt +++ b/src/test/kotlin/com/back/koreaTravelGuide/domain/ai/weather/client/WeatherApiClientTest.kt @@ -1,91 +1,94 @@ + import com.back.koreaTravelGuide.KoreaTravelGuideApplication import com.back.koreaTravelGuide.config.TestConfig -import com.back.koreaTravelGuide.domain.ai.weather.client.WeatherApiClient -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Assumptions.assumeTrue +import com.back.koreaTravelGuide.domain.ai.weather.dto.MidForecastDto +import com.back.koreaTravelGuide.domain.ai.weather.dto.TemperatureAndLandForecastDto +import com.back.koreaTravelGuide.domain.ai.weather.service.WeatherService import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test +import org.mockito.Mockito.`when` import org.springframework.beans.factory.annotation.Autowired -import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc import org.springframework.boot.test.context.SpringBootTest import org.springframework.context.annotation.Import +import org.springframework.http.MediaType import org.springframework.test.context.ActiveProfiles -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter +import org.springframework.test.context.bean.override.mockito.MockitoBean +import org.springframework.test.web.servlet.MockMvc +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.result.MockMvcResultHandlers.print +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status +import org.springframework.transaction.annotation.Transactional /** * ์‹ค์ œ ๊ธฐ์ƒ์ฒญ API ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•œ ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ. */ @SpringBootTest(classes = [KoreaTravelGuideApplication::class]) +@AutoConfigureMockMvc(addFilters = false) @ActiveProfiles("test") @Import(TestConfig::class) +@Transactional class WeatherApiClientTest { @Autowired - private lateinit var weatherApiClient: WeatherApiClient - - @Value("\${weather.api.key}") - private lateinit var serviceKey: String + private lateinit var mockMvc: MockMvc - private fun getCurrentBaseTime(): String { - val now = LocalDateTime.now() - val baseHour = if (now.hour >= 6) "0600" else "1800" - val date = if (now.hour >= 6) now else now.minusDays(1) - return date.format(DateTimeFormatter.ofPattern("yyyyMMdd")) + baseHour - } + @MockitoBean + private lateinit var weatherService: WeatherService @DisplayName("fetchMidForecast - ์‹ค์ œ ๊ธฐ์ƒ์ฒญ API ์ค‘๊ธฐ์ „๋ง์กฐํšŒ (๋ฐ์ดํ„ฐ ๊ธฐ๋Œ€)") @Test fun fetchMidForecastTest() { - assumeTrue(serviceKey.isNotBlank() && !serviceKey.contains("WEATHER_API_KEY")) { - "API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•„ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค." - } - - val regionId = "11B00000" - val baseTime = getCurrentBaseTime() - - println("=== Test Parameters ===") - println("regionId: $regionId") - println("baseTime: $baseTime") - - val result = weatherApiClient.fetchMidForecast(regionId, baseTime) - println("=== Result ===") - println("result: $result") - - // ํ˜„์žฌ API ์‘๋‹ต์ด null์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒƒ์ด ์ •์ƒ์ผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ - // assertThat(result).isNotNull() + val mockData = + listOf( + MidForecastDto( + regionCode = "11B00000", + baseTime = "2025101006", + precipitation = "๋ง‘์Œ", + temperature = "ํ‰๋…„๊ณผ ๋น„์Šท", + maritime = null, + variability = null, + ), + ) + `when`(weatherService.getWeatherForecast()).thenReturn(mockData) + + // when & then - API ํ˜ธ์ถœ ๋ฐ ๊ฒ€์ฆ + mockMvc.perform( + get("/weather/test1") + .contentType(MediaType.APPLICATION_JSON), + ) + .andExpect(status().isOk) + .andExpect(jsonPath("$[0].regionCode").value("11B00000")) + .andExpect(jsonPath("$[0].precipitation").value("๋ง‘์Œ")) + .andDo(print()) // ๊ฒฐ๊ณผ ์ถœ๋ ฅ } - @DisplayName("fetchTemperature - ์‹ค์ œ ๊ธฐ์ƒ์ฒญ API ์ค‘๊ธฐ๊ธฐ์˜จ์กฐํšŒ (๋ฐ์ดํ„ฐ ๊ธฐ๋Œ€)") + @DisplayName("TemperatureAndLandForecast - ์‹ค์ œ ๊ธฐ์ƒ์ฒญ API ์ค‘๊ธฐ๊ธฐ์˜จ์กฐํšŒ + ์ค‘๊ธฐ์œก์ƒ์˜ˆ๋ณด์กฐํšŒ(๋ฐ์ดํ„ฐ ๊ธฐ๋Œ€)") @Test fun fetchTemperatureTest() { - assumeTrue(serviceKey.isNotBlank() && !serviceKey.contains("WEATHER_API_KEY")) { - "API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•„ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค." - } - - val regionId = "11B10101" - val baseTime = getCurrentBaseTime() - - val result = weatherApiClient.fetchTemperature(regionId, baseTime) - println("=== Result ===") - println("result: $result") - - assertThat(result).isNotNull() - } - - @DisplayName("fetchLandForecast - ์‹ค์ œ ๊ธฐ์ƒ์ฒญ API ์ค‘๊ธฐ์œก์ƒ์˜ˆ๋ณด์กฐํšŒ (๋ฐ์ดํ„ฐ ๊ธฐ๋Œ€)") - @Test - fun fetchLandForecastTest() { - assumeTrue(serviceKey.isNotBlank() && !serviceKey.contains("WEATHER_API_KEY")) { - "API ํ‚ค๊ฐ€ ์„ค์ •๋˜์ง€ ์•Š์•„ ํ…Œ์ŠคํŠธ๋ฅผ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค." - } - - val regionId = "11B00000" - val baseTime = getCurrentBaseTime() - - val result = weatherApiClient.fetchLandForecast(regionId, baseTime) - println("=== Result ===") - println("result: $result") - - assertThat(result).isNotNull() + // given + val mockData = + listOf( + TemperatureAndLandForecastDto( + regionCode = "11B10101", + baseTime = "2025101006", + minTemp = 10, + maxTemp = 20, + minTempRange = "8~12", + maxTempRange = "18~22", + amRainPercent = 30, + pmRainPercent = 20, + amWeather = "๋ง‘์Œ", + pmWeather = "๊ตฌ๋ฆ„๋งŽ์Œ", + ), + ) + `when`(weatherService.getTemperatureAndLandForecast("11B10101")).thenReturn(mockData) + + // when & then + mockMvc.perform(get("/weather/test2")) + .andExpect(status().isOk) + .andExpect(jsonPath("$[0].regionCode").value("11B10101")) + .andExpect(jsonPath("$[0].minTemp").value(10)) + .andDo(print()) } }