Skip to content

Commit c9335dc

Browse files
Merge pull request #3 from MoscowSquad/feature/get-weather-info-usecase
Add unit tests for getCurrentWeatherUseCase to validate data accuracy and edge case handling
2 parents 6af5a2c + f4035b8 commit c9335dc

File tree

7 files changed

+168
-17
lines changed

7 files changed

+168
-17
lines changed

build.gradle.kts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,20 @@ dependencies {
2424
testImplementation("com.google.truth:truth:1.4.2")
2525
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
2626
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
27-
2827
implementation("io.ktor:ktor-client-core:2.3.13")
2928
implementation("io.ktor:ktor-client-cio:2.3.13")
3029
testImplementation("io.ktor:ktor-client-mock:2.3.13")
3130

3231
implementation("ch.qos.logback:logback-classic:1.5.6")
33-
3432
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
35-
}
33+
val ktor_version = "2.3.10"
34+
implementation("io.ktor:ktor-client-core:$ktor_version")
35+
implementation("io.ktor:ktor-client-cio:$ktor_version")
36+
implementation("io.ktor:ktor-client-content-negotiation:$ktor_version")
37+
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
38+
39+
40+
}
3641

3742
tasks.test {
3843
useJUnitPlatform()
Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,33 @@
11
package data.repository
22

3-
import domain.repository.WeatherRepository
3+
import WeatherRepository
4+
import domain.models.CurrentWeather
5+
import domain.models.exceptions.FailedFetchWeatherDataException
6+
import io.ktor.client.*
7+
import io.ktor.client.call.*
8+
import io.ktor.client.request.*
9+
import io.ktor.client.statement.*
10+
import io.ktor.http.*
411

5-
class WeatherRepositoryImpl : WeatherRepository
12+
const val OPEN_METO_API = "https://api.open-meteo.com/v1/forecast"
13+
14+
class WeatherRepositoryImpl(private val httpClient: HttpClient) : WeatherRepository {
15+
override suspend fun getCurrentWeather(latitude: Double, longitude: Double): CurrentWeather {
16+
val response: HttpResponse = httpClient.get(OPEN_METO_API) {
17+
parameter("latitude", latitude)
18+
parameter("longitude", longitude)
19+
parameter(
20+
"current",
21+
"temperature_2m,relative_humidity_2m,apparent_temperature,is_day,wind_speed_10m,snowfall,rain,weather_code,cloud_cover"
22+
)
23+
}
24+
25+
if (!response.status.isSuccess()) {
26+
throw FailedFetchWeatherDataException(
27+
"Weather API returned error status: ${response.status}"
28+
)
29+
}
30+
31+
return response.body()
32+
}
33+
}

src/main/kotlin/domain/models/CurrentWeather.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ data class CurrentWeather(
88
@SerialName("temperature_2m") val temperature2m: Double,
99
@SerialName("relative_humidity_2m") val relativeHumidity2m: Double,
1010
@SerialName("apparent_temperature") val apparentTemperature: Double,
11-
@SerialName("is_day") val isDay: Double,
12-
val precipitation: Double,
11+
@SerialName("is_day") val isDay: Int,
1312
val rain: Double,
1413
val snowfall: Double,
1514
@SerialName("cloud_cover") val cloudCover: Double,
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package domain.models.exceptions
2+
3+
class FailedFetchWeatherDataException(
4+
message: String,
5+
cause: Throwable? = null
6+
) : Exception(message, cause)
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
package domain.repository
1+
import domain.models.CurrentWeather
22

3-
interface WeatherRepository
3+
interface WeatherRepository {
4+
suspend fun getCurrentWeather(latitude: Double, longitude: Double): CurrentWeather
5+
}
Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
package domain.use_cases
1+
import domain.models.CurrentWeather
22

3-
import domain.repository.WeatherRepository
4-
5-
class GetCurrentWeatherUseCase(
6-
private val weatherRepository: WeatherRepository
7-
) {
8-
9-
}
3+
class GetCurrentWeatherUseCase(private val repository: WeatherRepository) {
4+
suspend fun getCurrentWeather(latitude: Double, longitude: Double): CurrentWeather {
5+
return repository.getCurrentWeather(latitude, longitude)
6+
}
7+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package domain.use_cases
2+
3+
import GetCurrentWeatherUseCase
4+
import WeatherRepository
5+
import com.google.common.truth.Truth
6+
import com.google.common.truth.Truth.assertThat
7+
import domain.models.CurrentWeather
8+
import io.mockk.coEvery
9+
import io.mockk.coVerify
10+
import io.mockk.mockk
11+
import kotlinx.coroutines.test.runTest
12+
import org.junit.jupiter.api.BeforeEach
13+
import org.junit.jupiter.api.Test
14+
import org.junit.jupiter.api.assertThrows
15+
16+
class GetCurrentWeatherUseCaseTest {
17+
18+
private lateinit var weatherRepository: WeatherRepository
19+
private lateinit var getCurrentWeatherUseCase: GetCurrentWeatherUseCase
20+
21+
private val latitude = 55.7558
22+
private val longitude = 37.6173
23+
private val expectedWeather = CurrentWeather(
24+
temperature2m = 12.5,
25+
relativeHumidity2m = 67.0,
26+
apparentTemperature = 11.2,
27+
isDay = 1,
28+
rain = 0.1,
29+
snowfall = 0.0,
30+
cloudCover = 65.0,
31+
windSpeed10m = 6.8,
32+
)
33+
34+
@BeforeEach
35+
fun setup() {
36+
weatherRepository = mockk(relaxed = true)
37+
getCurrentWeatherUseCase = GetCurrentWeatherUseCase(weatherRepository)
38+
}
39+
40+
@Test
41+
fun `should get the current weather from the WeatherRepository when getting the current weather`() =
42+
runTest {
43+
// Given
44+
coEvery { weatherRepository.getCurrentWeather(latitude, longitude) } returns expectedWeather
45+
46+
// When
47+
val result = getCurrentWeatherUseCase.getCurrentWeather(latitude, longitude)
48+
49+
// Then
50+
coVerify(exactly = 1) { weatherRepository.getCurrentWeather(latitude, longitude) }
51+
}
52+
53+
@Test
54+
fun `should return current weather for given coordinates when getting the current weather`() = runTest {
55+
// Given
56+
coEvery { weatherRepository.getCurrentWeather(latitude, longitude) } returns expectedWeather
57+
58+
// When
59+
val result = getCurrentWeatherUseCase.getCurrentWeather(latitude, longitude)
60+
61+
// Then
62+
Truth.assertThat(expectedWeather).isEqualTo(result)
63+
}
64+
65+
@Test
66+
67+
fun `fun getCurrentWeather() should throw exception when repository fails`() = runTest {
68+
69+
// Given
70+
val exceptionMessage = "API error"
71+
coEvery { weatherRepository.getCurrentWeather(latitude, longitude) } throws RuntimeException(exceptionMessage)
72+
73+
// When / Then
74+
val exception = assertThrows<RuntimeException> {
75+
getCurrentWeatherUseCase.getCurrentWeather(latitude, longitude)
76+
}
77+
78+
// Assert
79+
assertThat(exception.message).isEqualTo(exceptionMessage)
80+
}
81+
82+
@Test
83+
fun `getCurrentWeather() should return expected weather data when valid latitude and longitude are provided()`() =
84+
runTest {
85+
// Given
86+
val testLat = 33.3
87+
val testLon = 44.4
88+
val expected = expectedWeather.copy(temperature2m = 25.0)
89+
coEvery { weatherRepository.getCurrentWeather(testLat, testLon) } returns expected
90+
91+
// When
92+
val result = getCurrentWeatherUseCase.getCurrentWeather(testLat, testLon)
93+
94+
// Then
95+
coVerify { weatherRepository.getCurrentWeather(testLat, testLon) }
96+
assertThat(result).isEqualTo(expected)
97+
}
98+
99+
100+
@Test
101+
fun `getCurrentWeather() should call repository with zero coordinates when latitude and longitude are both zeros`() =
102+
runTest {
103+
val zeroLat = 0.0
104+
val zeroLon = 0.0
105+
coEvery { weatherRepository.getCurrentWeather(zeroLat, zeroLon) } returns expectedWeather
106+
107+
// When
108+
getCurrentWeatherUseCase.getCurrentWeather(zeroLat, zeroLon)
109+
110+
// Then
111+
coVerify { weatherRepository.getCurrentWeather(zeroLat, zeroLon) }
112+
}
113+
}

0 commit comments

Comments
 (0)