diff --git a/.idea/detekt.xml b/.idea/detekt.xml
index f69c092..0623675 100644
--- a/.idea/detekt.xml
+++ b/.idea/detekt.xml
@@ -1,7 +1,6 @@
- true
D:\AndroidLab\AndroidLab2\config\detekt\detekt.yml
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 526b4c2..a2d7c21 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -13,7 +13,6 @@
-
diff --git a/app/build.gradle b/app/build.gradle
index 15ac8f7..2a6a6fb 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -81,6 +81,7 @@ dependencies {
def coroutines = "1.6.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
+ testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines"
// region Network
def retrofit = "2.9.0"
@@ -104,8 +105,13 @@ dependencies {
implementation "it.czerwinski.android.hilt:hilt-extensions:${hiltExt}"
kapt "it.czerwinski.android.hilt:hilt-processor:${hiltExt}"
-
testImplementation 'junit:junit:4.13.2'
+ testImplementation 'io.mockk:mockk:1.12.3'
+ testRuntimeOnly "org.junit.vintage:junit-vintage-engine:5.8.2"
+ testImplementation "org.junit.jupiter:junit-jupiter-api:5.8.2"
+ testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.8.2"
+ testImplementation "org.jetbrains.kotlin:kotlin-test-junit5:1.6.21"
+
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/weather/WeatherViewModel.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/weather/WeatherViewModel.kt
index 4e85725..d9d12de 100644
--- a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/weather/WeatherViewModel.kt
+++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/weather/WeatherViewModel.kt
@@ -18,23 +18,6 @@ class WeatherViewModel @Inject constructor(
private val getWeatherUseCase: GetWeatherUseCase
) : ViewModel() {
-// @AssistedFactory
-// interface Factory {
-// fun create(@Assisted cityId: Int): Factory
-// }
-//
-// @Suppress("UNCHECKED_CAST")
-// companion object {
-// fun provideFactory(
-// assistedFactory: Factory,
-// cityId: Int
-// ): ViewModelProvider.Factory = object : ViewModelProvider.Factory {
-// override fun create(modelClass: Class): T {
-// return assistedFactory.create(cityId) as T
-// }
-// }
-// }
-
private var _weather: MutableLiveData> = MutableLiveData()
val weather: LiveData> get() = _weather
@@ -50,7 +33,3 @@ class WeatherViewModel @Inject constructor(
}
}
}
-
-//@Module
-//@InstallIn(ActivityRetainedComponent::class)
-//interface AssistedInjectModule
diff --git a/app/src/test/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherListUseCaseTest.kt b/app/src/test/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherListUseCaseTest.kt
new file mode 100644
index 0000000..9a61564
--- /dev/null
+++ b/app/src/test/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherListUseCaseTest.kt
@@ -0,0 +1,49 @@
+package ru.itis.karakurik.androidLab2.domain.usecase
+
+import io.mockk.*
+import ru.itis.karakurik.androidLab2.utils.MainCoroutineRule
+import io.mockk.impl.annotations.MockK
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.DisplayName
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import ru.itis.karakurik.androidLab2.domain.entity.Weather
+import ru.itis.karakurik.androidLab2.domain.repository.WeatherRepository
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(JUnit4::class)
+internal class GetWeatherListUseCaseTest {
+ @MockK
+ lateinit var repository: WeatherRepository
+
+ @get:Rule
+ val coroutineRule: MainCoroutineRule = MainCoroutineRule()
+
+ private lateinit var useCase: GetWeatherListUseCase
+
+ @Before
+ fun setUp() {
+ MockKAnnotations.init(this)
+ useCase = spyk(GetWeatherListUseCase(repository, coroutineRule.testDispatcher))
+ }
+
+ @Test
+ @DisplayName("Successful getting weather list")
+ operator fun invoke() = runBlocking {
+ val expectedList = arrayListOf(
+ mockk { every { id } returns 1 },
+ mockk { every { id } returns 2 },
+ )
+
+ coEvery { repository.getWeatherList(any(), any(), any()) } returns expectedList
+
+ val result = useCase(1.0, 1.0, 5)
+
+ assertEquals(expectedList, result)
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherUseCaseTest.kt b/app/src/test/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherUseCaseTest.kt
new file mode 100644
index 0000000..7eb8e2a
--- /dev/null
+++ b/app/src/test/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherUseCaseTest.kt
@@ -0,0 +1,48 @@
+package ru.itis.karakurik.androidLab2.domain.usecase
+
+import io.mockk.*
+import ru.itis.karakurik.androidLab2.utils.MainCoroutineRule
+import io.mockk.impl.annotations.MockK
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.DisplayName
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import ru.itis.karakurik.androidLab2.domain.entity.Weather
+import ru.itis.karakurik.androidLab2.domain.repository.WeatherRepository
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(JUnit4::class)
+internal class GetWeatherUseCaseTest {
+ @MockK
+ lateinit var repository: WeatherRepository
+
+ @get:Rule
+ val coroutineRule: MainCoroutineRule = MainCoroutineRule()
+
+ private lateinit var useCase: GetWeatherUseCase
+
+ @Before
+ fun setUp() {
+ MockKAnnotations.init(this)
+ useCase = spyk(GetWeatherUseCase(repository, coroutineRule.testDispatcher))
+ }
+
+ @Test
+ @DisplayName("Successful getting weather")
+ operator fun invoke() = runBlocking {
+ val expected = mockk() {
+ every { id } returns 1
+ }
+
+ coEvery { repository.getWeather(1) } returns expected
+
+ val result = useCase(1)
+
+ assertEquals(expected, result)
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/ru/itis/karakurik/androidLab2/presentation/fragments/cities/CityListViewModelTest.kt b/app/src/test/java/ru/itis/karakurik/androidLab2/presentation/fragments/cities/CityListViewModelTest.kt
new file mode 100644
index 0000000..cb9fb90
--- /dev/null
+++ b/app/src/test/java/ru/itis/karakurik/androidLab2/presentation/fragments/cities/CityListViewModelTest.kt
@@ -0,0 +1,65 @@
+package ru.itis.karakurik.androidLab2.presentation.fragments.cities
+
+import io.mockk.MockKAnnotations
+import io.mockk.coEvery
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.mockk
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.jupiter.api.Assertions.*
+import ru.itis.karakurik.androidLab2.domain.entity.Weather
+import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherListUseCase
+import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherUseCase
+import ru.itis.karakurik.androidLab2.utils.getOrAwaitValue
+
+internal class WeatherViewModelTest {
+
+ @MockK
+ lateinit var getWeatherUseCase: GetWeatherUseCase
+
+ @MockK
+ lateinit var getWeatherListUseCase: GetWeatherListUseCase
+
+ private lateinit var viewModel: CityListViewModel
+
+ @Before
+ fun setUp() {
+ MockKAnnotations.init(this)
+ viewModel = CityListViewModel(
+ getWeatherUseCase,
+ getWeatherListUseCase
+ )
+ }
+
+ @Test
+ fun getCityIdByName() = runBlocking {
+
+ val mockCityId = mockk { every { id } returns 1 }.id
+ val expectedCityId = Result.success(mockCityId)
+
+ coEvery { getWeatherUseCase("Kazan").id } returns mockCityId
+
+ viewModel.onGetCityId("Kazan")
+
+ assertEquals(expectedCityId, viewModel.cityId.getOrAwaitValue())
+ }
+
+ @Test
+ fun getWeatherList() = runBlocking {
+
+ val mockCityList = arrayListOf(
+ mockk { every { id } returns 1 },
+ mockk { every { id } returns 2 },
+ )
+
+ val expectedWeather = Result.success(mockCityList)
+
+ coEvery { getWeatherListUseCase(any(), any(), any()) } returns mockCityList
+
+ viewModel.onGetWeatherList(1.0, 1.0, 5)
+
+ assertEquals(expectedWeather, viewModel.weatherList.getOrAwaitValue())
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/ru/itis/karakurik/androidLab2/presentation/fragments/weather/WeatherViewModelTest.kt b/app/src/test/java/ru/itis/karakurik/androidLab2/presentation/fragments/weather/WeatherViewModelTest.kt
new file mode 100644
index 0000000..2551387
--- /dev/null
+++ b/app/src/test/java/ru/itis/karakurik/androidLab2/presentation/fragments/weather/WeatherViewModelTest.kt
@@ -0,0 +1,42 @@
+package ru.itis.karakurik.androidLab2.presentation.fragments.weather
+
+import io.mockk.MockKAnnotations
+import io.mockk.coEvery
+import io.mockk.every
+import io.mockk.impl.annotations.MockK
+import io.mockk.mockk
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.jupiter.api.Assertions.*
+import org.junit.jupiter.api.DisplayName
+import ru.itis.karakurik.androidLab2.domain.entity.Weather
+import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherUseCase
+import ru.itis.karakurik.androidLab2.utils.getOrAwaitValue
+
+internal class WeatherViewModelTest {
+
+ @MockK
+ lateinit var getWeatherUseCase: GetWeatherUseCase
+
+ private lateinit var viewModel: WeatherViewModel
+
+ @Before
+ fun setUp() {
+ MockKAnnotations.init(this)
+ viewModel = WeatherViewModel(getWeatherUseCase)
+ }
+
+ @Test
+ fun getWeatherById() = runBlocking {
+
+ val mockWeather = mockk { every { id } returns 1 }
+ val expectedWeather = Result.success(mockWeather)
+
+ coEvery { getWeatherUseCase(1) } returns mockWeather
+
+ viewModel.onGetWeather(1)
+
+ assertEquals(expectedWeather, viewModel.weather.getOrAwaitValue())
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/ru/itis/karakurik/androidLab2/utils/MainCoroutineRule.kt b/app/src/test/java/ru/itis/karakurik/androidLab2/utils/MainCoroutineRule.kt
new file mode 100644
index 0000000..0b26a40
--- /dev/null
+++ b/app/src/test/java/ru/itis/karakurik/androidLab2/utils/MainCoroutineRule.kt
@@ -0,0 +1,28 @@
+package ru.itis.karakurik.androidLab2.utils
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+import kotlin.coroutines.ContinuationInterceptor
+
+@ExperimentalCoroutinesApi
+class MainCoroutineRule : TestWatcher(), TestCoroutineScope by TestCoroutineScope() {
+
+ val testDispatcher = StandardTestDispatcher()
+
+ override fun starting(description: Description) {
+ super.starting(description)
+ Dispatchers.setMain(this.coroutineContext[ContinuationInterceptor] as CoroutineDispatcher)
+ }
+
+ override fun finished(description: Description) {
+ super.finished(description)
+ Dispatchers.resetMain()
+ }
+}
diff --git a/app/src/test/java/ru/itis/karakurik/androidLab2/utils/getOrAwaitValue.kt b/app/src/test/java/ru/itis/karakurik/androidLab2/utils/getOrAwaitValue.kt
new file mode 100644
index 0000000..d466020
--- /dev/null
+++ b/app/src/test/java/ru/itis/karakurik/androidLab2/utils/getOrAwaitValue.kt
@@ -0,0 +1,35 @@
+package ru.itis.karakurik.androidLab2.utils
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+
+fun LiveData.getOrAwaitValue(
+ time: Long = 2,
+ timeUnit: TimeUnit = TimeUnit.SECONDS,
+ afterObserve: () -> Unit = {}
+): T {
+ var data: T? = null
+ val latch = CountDownLatch(1)
+ val observer = object : Observer {
+ override fun onChanged(o: T?) {
+ data = o
+ latch.countDown()
+ this@getOrAwaitValue.removeObserver(this)
+ }
+ }
+ this.observeForever(observer)
+
+ afterObserve.invoke()
+
+ // Don't wait indefinitely if the LiveData is not set.
+ if (!latch.await(time, timeUnit)) {
+ this.removeObserver(observer)
+ throw TimeoutException("LiveData value was never set.")
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ return data as T
+}