Add ViewModel tests
This commit is contained in:
@@ -72,6 +72,7 @@ class RecipesListViewModel @Inject constructor(
|
|||||||
logger.v { "onDeleteConfirm() called with: recipeSummaryEntity = $recipeSummaryEntity" }
|
logger.v { "onDeleteConfirm() called with: recipeSummaryEntity = $recipeSummaryEntity" }
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val result = recipeRepo.deleteRecipe(recipeSummaryEntity)
|
val result = recipeRepo.deleteRecipe(recipeSummaryEntity)
|
||||||
|
logger.d { "onDeleteConfirm: delete result is $result" }
|
||||||
_deleteRecipeResult.emit(result)
|
_deleteRecipeResult.emit(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,23 @@ import com.google.common.truth.Truth.assertThat
|
|||||||
import gq.kirmanak.mealient.data.auth.AuthRepo
|
import gq.kirmanak.mealient.data.auth.AuthRepo
|
||||||
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
import gq.kirmanak.mealient.data.recipes.RecipeRepo
|
||||||
import gq.kirmanak.mealient.test.BaseUnitTest
|
import gq.kirmanak.mealient.test.BaseUnitTest
|
||||||
|
import gq.kirmanak.mealient.test.RecipeImplTestData.CAKE_RECIPE_SUMMARY_ENTITY
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coVerify
|
import io.mockk.coVerify
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.impl.annotations.MockK
|
import io.mockk.impl.annotations.MockK
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.flow.toList
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class RecipesListViewModelTest : BaseUnitTest() {
|
class RecipesListViewModelTest : BaseUnitTest() {
|
||||||
@@ -24,6 +32,12 @@ class RecipesListViewModelTest : BaseUnitTest() {
|
|||||||
@MockK(relaxed = true)
|
@MockK(relaxed = true)
|
||||||
lateinit var recipeRepo: RecipeRepo
|
lateinit var recipeRepo: RecipeRepo
|
||||||
|
|
||||||
|
@Before
|
||||||
|
override fun setUp() {
|
||||||
|
super.setUp()
|
||||||
|
every { authRepo.isAuthorizedFlow } returns flowOf(true)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when authRepo isAuthorized changes to true expect that recipes are refreshed`() {
|
fun `when authRepo isAuthorized changes to true expect that recipes are refreshed`() {
|
||||||
every { authRepo.isAuthorizedFlow } returns flowOf(false, true)
|
every { authRepo.isAuthorizedFlow } returns flowOf(false, true)
|
||||||
@@ -40,14 +54,12 @@ class RecipesListViewModelTest : BaseUnitTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when authRepo isAuthorized doesn't change expect that recipes are not refreshed`() {
|
fun `when authRepo isAuthorized doesn't change expect that recipes are not refreshed`() {
|
||||||
every { authRepo.isAuthorizedFlow } returns flowOf(true)
|
|
||||||
createSubject()
|
createSubject()
|
||||||
coVerify(inverse = true) { recipeRepo.refreshRecipes() }
|
coVerify(inverse = true) { recipeRepo.refreshRecipes() }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when refreshRecipeInfo succeeds expect successful result`() = runTest {
|
fun `when refreshRecipeInfo succeeds expect successful result`() = runTest {
|
||||||
every { authRepo.isAuthorizedFlow } returns flowOf(true)
|
|
||||||
val slug = "cake"
|
val slug = "cake"
|
||||||
coEvery { recipeRepo.refreshRecipeInfo(eq(slug)) } returns Result.success(Unit)
|
coEvery { recipeRepo.refreshRecipeInfo(eq(slug)) } returns Result.success(Unit)
|
||||||
val actual = createSubject().refreshRecipeInfo(slug).asFlow().first()
|
val actual = createSubject().refreshRecipeInfo(slug).asFlow().first()
|
||||||
@@ -56,7 +68,6 @@ class RecipesListViewModelTest : BaseUnitTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when refreshRecipeInfo succeeds expect call to repo`() = runTest {
|
fun `when refreshRecipeInfo succeeds expect call to repo`() = runTest {
|
||||||
every { authRepo.isAuthorizedFlow } returns flowOf(true)
|
|
||||||
val slug = "cake"
|
val slug = "cake"
|
||||||
coEvery { recipeRepo.refreshRecipeInfo(eq(slug)) } returns Result.success(Unit)
|
coEvery { recipeRepo.refreshRecipeInfo(eq(slug)) } returns Result.success(Unit)
|
||||||
createSubject().refreshRecipeInfo(slug).asFlow().first()
|
createSubject().refreshRecipeInfo(slug).asFlow().first()
|
||||||
@@ -65,7 +76,6 @@ class RecipesListViewModelTest : BaseUnitTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when refreshRecipeInfo fails expect result with error`() = runTest {
|
fun `when refreshRecipeInfo fails expect result with error`() = runTest {
|
||||||
every { authRepo.isAuthorizedFlow } returns flowOf(true)
|
|
||||||
val slug = "cake"
|
val slug = "cake"
|
||||||
val result = Result.failure<Unit>(RuntimeException())
|
val result = Result.failure<Unit>(RuntimeException())
|
||||||
coEvery { recipeRepo.refreshRecipeInfo(eq(slug)) } returns result
|
coEvery { recipeRepo.refreshRecipeInfo(eq(slug)) } returns result
|
||||||
@@ -73,5 +83,38 @@ class RecipesListViewModelTest : BaseUnitTest() {
|
|||||||
assertThat(actual).isEqualTo(result)
|
assertThat(actual).isEqualTo(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when delete recipe expect successful result in flow`() = runTest {
|
||||||
|
coEvery { recipeRepo.deleteRecipe(any()) } returns Result.success(Unit)
|
||||||
|
val subject = createSubject()
|
||||||
|
val results = runTestAndCollectFlow(subject.deleteRecipeResult) {
|
||||||
|
subject.onDeleteConfirm(CAKE_RECIPE_SUMMARY_ENTITY)
|
||||||
|
}
|
||||||
|
assertThat(results.single().isSuccess).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `when delete recipe expect failed result in flow`() = runTest {
|
||||||
|
coEvery { recipeRepo.deleteRecipe(any()) } returns Result.failure(IOException())
|
||||||
|
val subject = createSubject()
|
||||||
|
val results = runTestAndCollectFlow(subject.deleteRecipeResult) {
|
||||||
|
subject.onDeleteConfirm(CAKE_RECIPE_SUMMARY_ENTITY)
|
||||||
|
}
|
||||||
|
assertThat(results.single().isFailure).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <T> TestScope.runTestAndCollectFlow(
|
||||||
|
flow: Flow<T>,
|
||||||
|
block: () -> Unit,
|
||||||
|
): List<T> {
|
||||||
|
val results = mutableListOf<T>()
|
||||||
|
val collectJob = launch(UnconfinedTestDispatcher(testScheduler)) {
|
||||||
|
flow.toList(results)
|
||||||
|
}
|
||||||
|
block()
|
||||||
|
collectJob.cancel()
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
private fun createSubject() = RecipesListViewModel(recipeRepo, authRepo, logger)
|
private fun createSubject() = RecipesListViewModel(recipeRepo, authRepo, logger)
|
||||||
}
|
}
|
||||||
@@ -15,9 +15,7 @@ import kotlinx.coroutines.flow.take
|
|||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.Timeout
|
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class ShareRecipeViewModelTest : BaseUnitTest() {
|
class ShareRecipeViewModelTest : BaseUnitTest() {
|
||||||
@@ -27,9 +25,6 @@ class ShareRecipeViewModelTest : BaseUnitTest() {
|
|||||||
|
|
||||||
lateinit var subject: ShareRecipeViewModel
|
lateinit var subject: ShareRecipeViewModel
|
||||||
|
|
||||||
@get:Rule
|
|
||||||
val timeoutRule: Timeout = Timeout.seconds(5)
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
override fun setUp() {
|
override fun setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
|
|||||||
@@ -7,20 +7,23 @@ import io.mockk.MockKAnnotations
|
|||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
|
||||||
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
import kotlinx.coroutines.test.resetMain
|
import kotlinx.coroutines.test.resetMain
|
||||||
import kotlinx.coroutines.test.setMain
|
import kotlinx.coroutines.test.setMain
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
import org.junit.rules.Timeout
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
open class BaseUnitTest {
|
open class BaseUnitTest {
|
||||||
|
|
||||||
@get:Rule
|
@get:Rule(order = 0)
|
||||||
val instantExecutorRule = InstantTaskExecutorRule()
|
val instantExecutorRule = InstantTaskExecutorRule()
|
||||||
|
|
||||||
|
@get:Rule(order = 1)
|
||||||
|
val timeoutRule: Timeout = Timeout.seconds(10)
|
||||||
|
|
||||||
protected val logger: Logger = FakeLogger()
|
protected val logger: Logger = FakeLogger()
|
||||||
|
|
||||||
lateinit var dispatchers: AppDispatchers
|
lateinit var dispatchers: AppDispatchers
|
||||||
@@ -30,10 +33,10 @@ open class BaseUnitTest {
|
|||||||
MockKAnnotations.init(this)
|
MockKAnnotations.init(this)
|
||||||
Dispatchers.setMain(UnconfinedTestDispatcher())
|
Dispatchers.setMain(UnconfinedTestDispatcher())
|
||||||
dispatchers = object : AppDispatchers {
|
dispatchers = object : AppDispatchers {
|
||||||
override val io: CoroutineDispatcher = StandardTestDispatcher()
|
override val io: CoroutineDispatcher = UnconfinedTestDispatcher()
|
||||||
override val main: CoroutineDispatcher = StandardTestDispatcher()
|
override val main: CoroutineDispatcher = UnconfinedTestDispatcher()
|
||||||
override val default: CoroutineDispatcher = StandardTestDispatcher()
|
override val default: CoroutineDispatcher = UnconfinedTestDispatcher()
|
||||||
override val unconfined: CoroutineDispatcher = StandardTestDispatcher()
|
override val unconfined: CoroutineDispatcher = UnconfinedTestDispatcher()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user