From 45a59f17958e93b7085dfed13652b8947ffd4168 Mon Sep 17 00:00:00 2001 From: hifeful Date: Sat, 19 Aug 2023 20:20:49 +0300 Subject: [PATCH 1/2] - unit tests for a MealManiaViewModel, MealDetailsViewState, and Reducer of the MealDetailsFeature were added --- app/build.gradle | 5 + .../presentation/MealManiaViewModelTest.kt | 121 ++++++++++++++++++ .../details/MealDetailsReducerTest.kt | 35 +++++ .../details/MealDetailsViewStateTest.kt | 76 +++++++++++ 4 files changed, 237 insertions(+) create mode 100644 app/src/test/java/com/hifeful/mealmania/presentation/MealManiaViewModelTest.kt create mode 100644 app/src/test/java/com/hifeful/mealmania/presentation/details/MealDetailsReducerTest.kt create mode 100644 app/src/test/java/com/hifeful/mealmania/presentation/details/MealDetailsViewStateTest.kt diff --git a/app/build.gradle b/app/build.gradle index 4700b60..21db65b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -90,6 +90,11 @@ dependencies { // Testing testImplementation 'junit:junit:4.13.2' + testImplementation "org.robolectric:robolectric:4.10.3" + testImplementation 'com.android.support.test:rules:1.0.2' + testImplementation 'com.android.support.test:runner:1.0.2' + testImplementation "androidx.test:core:1.5.0" + testImplementation "androidx.test:core-ktx:1.5.0" androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } \ No newline at end of file diff --git a/app/src/test/java/com/hifeful/mealmania/presentation/MealManiaViewModelTest.kt b/app/src/test/java/com/hifeful/mealmania/presentation/MealManiaViewModelTest.kt new file mode 100644 index 0000000..84410bd --- /dev/null +++ b/app/src/test/java/com/hifeful/mealmania/presentation/MealManiaViewModelTest.kt @@ -0,0 +1,121 @@ +package com.hifeful.mealmania.presentation + +import android.net.Uri +import android.support.test.runner.AndroidJUnit4 +import com.hifeful.mealmania.presentation.util.DeeplinkResult +import org.junit.Test +import org.junit.runner.RunWith + + +@RunWith(AndroidJUnit4::class) +class MealManiaViewModelTest { + private val viewModel = MealManiaViewModel() + + @Test + fun `Given mymeals page deeplink, When handleDeeplink is called, Then ShowMyMealsPage DeeplinkResult should be returned`() { + // Given + val uri = Uri.parse("mealmania://mymeals") + + // When + val actual = viewModel.handleDeeplink(uri) + + // Then + assert(actual is DeeplinkResult.ShowMyMealsPage) + } + + @Test + fun `Given meals search deeplink, When handleDeeplink is called, Then ApplyMealsSearch DeeplinkResult should be returned`() { + // Given + val uri = Uri.parse("mealmania://search?q=soup") + + // When + val actual = viewModel.handleDeeplink(uri) + + // Then + assert(actual == DeeplinkResult.ApplyMealsSearch("soup")) + } + + @Test + fun `Given meals details page deeplink, When handleDeeplink is called, Then ShowMealsPage DeeplinkResult should be returned`() { + // Given + val uri = Uri.parse("mealmania://meal?id=123") + + // When + val actual = viewModel.handleDeeplink(uri) + + // Then + assert(actual == DeeplinkResult.ShowMealsPage("123")) + } + + @Test + fun `Given not supported deeplink, When handleDeeplink is called, Then null should be returned`() { + // Given + val uri = Uri.parse("mealmania://receipt?id=123&name=potato") + + // When + val actual = viewModel.handleDeeplink(uri) + + // Then + assert(actual == null) + } + + @Test + fun `Given unknown deeplink, When handleDeeplink is called, Then null should be returned`() { + // Given + val uri = Uri.parse("unknown") + + // When + val actual = viewModel.handleDeeplink(uri) + + // Then + assert(actual == null) + } + + @Test + fun `Given mymeals page firebase dynamic link, When handleFirebaseDynamicLink is called, Then ShowMyMealsPage DeeplinkResult should be returned`() { + // Given + val uri = Uri.parse("https://mealmania.page.link/mymeals") + + // When + val actual = viewModel.handleFirebaseDynamicLink(uri) + + // Then + assert(actual is DeeplinkResult.ShowMyMealsPage) + } + + @Test + fun `Given meals search firebase dynamic link, When handleFirebaseDynamicLink is called, Then ApplyMealsSearch DeeplinkResult should be returned`() { + // Given + val uri = Uri.parse("https://mealmania.link/search?q=soup") + + // When + val actual = viewModel.handleFirebaseDynamicLink(uri) + + // Then + assert(actual == DeeplinkResult.ApplyMealsSearch("soup")) + } + + @Test + fun `Given meals details page firebase dynamic link, When handleFirebaseDynamicLink is called, Then ShowMealsPage DeeplinkResult should be returned`() { + // Given + val uri = Uri.parse("https://mealmania.link/meal?id=123") + + // When + val actual = viewModel.handleFirebaseDynamicLink(uri) + + // Then + assert(actual == DeeplinkResult.ShowMealsPage("123")) + } + + @Test + fun `Given not supported firebase dynamic link, When handleFirebaseDynamicLink is called, Then null should be returned`() { + // Given + val uri = Uri.parse("https://mealmania.link/receipt?id=123&name=potato") + + // When + val actual = viewModel.handleFirebaseDynamicLink(uri) + + // Then + assert(actual == null) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/hifeful/mealmania/presentation/details/MealDetailsReducerTest.kt b/app/src/test/java/com/hifeful/mealmania/presentation/details/MealDetailsReducerTest.kt new file mode 100644 index 0000000..0553389 --- /dev/null +++ b/app/src/test/java/com/hifeful/mealmania/presentation/details/MealDetailsReducerTest.kt @@ -0,0 +1,35 @@ +package com.hifeful.mealmania.presentation.details + +import org.junit.Test + + +class MealDetailsReducerTest { + + private val reducer = MealDetailsFeature.ReducerImpl() + + @Test + fun `Given not favorite meal, When invoke is called, Then the state should be favorite`() { + // Given + val previousState = MealDetailsFeature.State(isFavourite = false) + val effect = MealDetailsFeature.Effect.FavouriteClicked(1) + + // When + val actual = reducer.invoke(previousState, effect) + + // Then + assert(actual.isFavourite == true) + } + + @Test + fun `Given favorite meal, When invoke is called, Then the state should be not favorite`() { + // Given + val previousState = MealDetailsFeature.State(isFavourite = true) + val effect = MealDetailsFeature.Effect.FavouriteClicked(1) + + // When + val actual = reducer.invoke(previousState, effect) + + // Then + assert(actual.isFavourite == false) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/hifeful/mealmania/presentation/details/MealDetailsViewStateTest.kt b/app/src/test/java/com/hifeful/mealmania/presentation/details/MealDetailsViewStateTest.kt new file mode 100644 index 0000000..16c2436 --- /dev/null +++ b/app/src/test/java/com/hifeful/mealmania/presentation/details/MealDetailsViewStateTest.kt @@ -0,0 +1,76 @@ +package com.hifeful.mealmania.presentation.details + +import android.content.Context +import android.content.res.ColorStateList +import android.support.test.runner.AndroidJUnit4 +import androidx.core.content.ContextCompat +import androidx.test.core.app.ApplicationProvider +import com.hifeful.mealmania.domain.model.Ingredient +import com.hifeful.mealmania.domain.model.Meal +import org.junit.Test +import org.junit.runner.RunWith + + +@RunWith(AndroidJUnit4::class) +class MealDetailsViewStateTest { + + private val context: Context = ApplicationProvider.getApplicationContext() + + @Test + fun `Given favorite meal, When getFavouriteIcon is called, Then holo red dark should be returned`() { + // Given + val expectedColorStateList = ColorStateList.valueOf( + ContextCompat.getColor(context, android.R.color.holo_red_dark) + ) + val viewState = MealDetailsViewState( + meal = provideMeal(), + isFavourite = true + ) + + // When + val actual = viewState.getFavouriteIcon(context) + + // Then + assert(actual == expectedColorStateList) + } + + @Test + fun `Given not favorite meal, When getFavouriteIcon is called, Then while color should be returned`() { + // Given + val expectedColorStateList = ColorStateList.valueOf( + ContextCompat.getColor(context, android.R.color.white) + ) + val viewState = MealDetailsViewState( + meal = provideMeal(), + ) + + // When + val actual = viewState.getFavouriteIcon(context) + + // Then + assert(actual == expectedColorStateList) + } + + private fun provideMeal( + id: String = "", + name: String = "", + category: String = "", + area: String = "", + instructions: String = "", + thumbnail: String = "", + drinkAlternate: String? = null, + youtubeSource: String? = null, + ingredients: List = emptyList() + ): Meal = + Meal( + id = id, + name = name, + category = category, + area = area, + instructions = instructions, + thumbnail = thumbnail, + drinkAlternate = drinkAlternate, + youtubeSource = youtubeSource, + ingredients = ingredients + ) +} \ No newline at end of file From 26004ba8e7d28c17edb1dbad0eb6e17be1a4056b Mon Sep 17 00:00:00 2001 From: hifeful Date: Tue, 22 Aug 2023 02:41:36 +0300 Subject: [PATCH 2/2] - AssertJ and mockK libraries were added and used for the unit tests - mocks instead of real objects are used in the MealDetailsViewStateTest now - test titles in the MealManiaViewModelTest.kt were changed - comments were removed --- app/build.gradle | 2 + .../presentation/MealManiaViewModelTest.kt | 68 +++++++------------ .../details/MealDetailsViewStateTest.kt | 30 +------- 3 files changed, 28 insertions(+), 72 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 21db65b..672c226 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,6 +95,8 @@ dependencies { testImplementation 'com.android.support.test:runner:1.0.2' testImplementation "androidx.test:core:1.5.0" testImplementation "androidx.test:core-ktx:1.5.0" + testImplementation 'org.assertj:assertj-core:3.24.2' + testImplementation "io.mockk:mockk:1.13.7" androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' } \ No newline at end of file diff --git a/app/src/test/java/com/hifeful/mealmania/presentation/MealManiaViewModelTest.kt b/app/src/test/java/com/hifeful/mealmania/presentation/MealManiaViewModelTest.kt index 84410bd..2e145cf 100644 --- a/app/src/test/java/com/hifeful/mealmania/presentation/MealManiaViewModelTest.kt +++ b/app/src/test/java/com/hifeful/mealmania/presentation/MealManiaViewModelTest.kt @@ -3,6 +3,7 @@ package com.hifeful.mealmania.presentation import android.net.Uri import android.support.test.runner.AndroidJUnit4 import com.hifeful.mealmania.presentation.util.DeeplinkResult +import org.assertj.core.api.Assertions import org.junit.Test import org.junit.runner.RunWith @@ -12,110 +13,87 @@ class MealManiaViewModelTest { private val viewModel = MealManiaViewModel() @Test - fun `Given mymeals page deeplink, When handleDeeplink is called, Then ShowMyMealsPage DeeplinkResult should be returned`() { - // Given + fun `with mymeals page deeplink - on handleDeeplink - it should return ShowMyMealsPage DeeplinkResult`() { val uri = Uri.parse("mealmania://mymeals") - // When val actual = viewModel.handleDeeplink(uri) - // Then - assert(actual is DeeplinkResult.ShowMyMealsPage) + Assertions.assertThat(actual).isInstanceOf(DeeplinkResult.ShowMyMealsPage::class.java) } @Test - fun `Given meals search deeplink, When handleDeeplink is called, Then ApplyMealsSearch DeeplinkResult should be returned`() { - // Given + fun `with meals search deeplink - on handleDeeplink - it should return ApplyMealsSearch DeeplinkResult`() { val uri = Uri.parse("mealmania://search?q=soup") - // When val actual = viewModel.handleDeeplink(uri) - // Then - assert(actual == DeeplinkResult.ApplyMealsSearch("soup")) + Assertions.assertThat(actual) + .isEqualTo(DeeplinkResult.ApplyMealsSearch("soup")) } @Test - fun `Given meals details page deeplink, When handleDeeplink is called, Then ShowMealsPage DeeplinkResult should be returned`() { - // Given + fun `with meals details page deeplink - on handleDeeplink - it should return ShowMealsPage DeeplinkResult`() { val uri = Uri.parse("mealmania://meal?id=123") - // When val actual = viewModel.handleDeeplink(uri) - // Then - assert(actual == DeeplinkResult.ShowMealsPage("123")) + Assertions.assertThat(actual) + .isEqualTo(DeeplinkResult.ShowMealsPage("123")) } @Test - fun `Given not supported deeplink, When handleDeeplink is called, Then null should be returned`() { - // Given + fun `with unsupported deeplink - on handleDeeplink - it should return no deeplink`() { val uri = Uri.parse("mealmania://receipt?id=123&name=potato") - // When val actual = viewModel.handleDeeplink(uri) - // Then - assert(actual == null) + Assertions.assertThat(actual).isNull() } @Test - fun `Given unknown deeplink, When handleDeeplink is called, Then null should be returned`() { - // Given + fun `with unknown deeplink - on handleDeeplink - it should return null`() { val uri = Uri.parse("unknown") - // When val actual = viewModel.handleDeeplink(uri) - // Then - assert(actual == null) + Assertions.assertThat(actual).isNull() } @Test - fun `Given mymeals page firebase dynamic link, When handleFirebaseDynamicLink is called, Then ShowMyMealsPage DeeplinkResult should be returned`() { - // Given + fun `with mymeals page firebase dynamic link - on handleFirebaseDynamicLink - it should return ShowMyMealsPage DeeplinkResult`() { val uri = Uri.parse("https://mealmania.page.link/mymeals") - // When val actual = viewModel.handleFirebaseDynamicLink(uri) - // Then - assert(actual is DeeplinkResult.ShowMyMealsPage) + Assertions.assertThat(actual).isInstanceOf(DeeplinkResult.ShowMyMealsPage::class.java) } @Test - fun `Given meals search firebase dynamic link, When handleFirebaseDynamicLink is called, Then ApplyMealsSearch DeeplinkResult should be returned`() { - // Given + fun `with meals search firebase dynamic link - on handleFirebaseDynamicLink - it should return ApplyMealsSearch DeeplinkResult`() { val uri = Uri.parse("https://mealmania.link/search?q=soup") - // When val actual = viewModel.handleFirebaseDynamicLink(uri) - // Then - assert(actual == DeeplinkResult.ApplyMealsSearch("soup")) + Assertions.assertThat(actual) + .isEqualTo(DeeplinkResult.ApplyMealsSearch("soup")) } @Test - fun `Given meals details page firebase dynamic link, When handleFirebaseDynamicLink is called, Then ShowMealsPage DeeplinkResult should be returned`() { - // Given + fun `with meals details page firebase dynamic link - on handleFirebaseDynamicLink - it should return ShowMealsPage DeeplinkResult`() { val uri = Uri.parse("https://mealmania.link/meal?id=123") - // When val actual = viewModel.handleFirebaseDynamicLink(uri) - // Then - assert(actual == DeeplinkResult.ShowMealsPage("123")) + Assertions.assertThat(actual) + .isEqualTo(DeeplinkResult.ShowMealsPage("123")) } @Test - fun `Given not supported firebase dynamic link, When handleFirebaseDynamicLink is called, Then null should be returned`() { - // Given + fun `with unsupported firebase dynamic link - on handleFirebaseDynamicLink - it should return null`() { val uri = Uri.parse("https://mealmania.link/receipt?id=123&name=potato") - // When val actual = viewModel.handleFirebaseDynamicLink(uri) - // Then - assert(actual == null) + Assertions.assertThat(actual).isNull() } } \ No newline at end of file diff --git a/app/src/test/java/com/hifeful/mealmania/presentation/details/MealDetailsViewStateTest.kt b/app/src/test/java/com/hifeful/mealmania/presentation/details/MealDetailsViewStateTest.kt index 16c2436..f218d83 100644 --- a/app/src/test/java/com/hifeful/mealmania/presentation/details/MealDetailsViewStateTest.kt +++ b/app/src/test/java/com/hifeful/mealmania/presentation/details/MealDetailsViewStateTest.kt @@ -5,8 +5,7 @@ import android.content.res.ColorStateList import android.support.test.runner.AndroidJUnit4 import androidx.core.content.ContextCompat import androidx.test.core.app.ApplicationProvider -import com.hifeful.mealmania.domain.model.Ingredient -import com.hifeful.mealmania.domain.model.Meal +import io.mockk.mockk import org.junit.Test import org.junit.runner.RunWith @@ -23,7 +22,7 @@ class MealDetailsViewStateTest { ContextCompat.getColor(context, android.R.color.holo_red_dark) ) val viewState = MealDetailsViewState( - meal = provideMeal(), + meal = mockk(), isFavourite = true ) @@ -41,7 +40,7 @@ class MealDetailsViewStateTest { ContextCompat.getColor(context, android.R.color.white) ) val viewState = MealDetailsViewState( - meal = provideMeal(), + meal = mockk(), ) // When @@ -50,27 +49,4 @@ class MealDetailsViewStateTest { // Then assert(actual == expectedColorStateList) } - - private fun provideMeal( - id: String = "", - name: String = "", - category: String = "", - area: String = "", - instructions: String = "", - thumbnail: String = "", - drinkAlternate: String? = null, - youtubeSource: String? = null, - ingredients: List = emptyList() - ): Meal = - Meal( - id = id, - name = name, - category = category, - area = area, - instructions = instructions, - thumbnail = thumbnail, - drinkAlternate = drinkAlternate, - youtubeSource = youtubeSource, - ingredients = ingredients - ) } \ No newline at end of file