issue#6 Add DisplayName annotation to ViewModel tests in app module

This commit is contained in:
Gergely Hegedus 2022-01-04 14:34:46 +02:00
parent e7f840a1b9
commit ab3e6a64f0
17 changed files with 92 additions and 53 deletions

View file

@ -3,6 +3,7 @@ package org.fnives.test.showcase.ui.home
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
@ -52,7 +53,7 @@ class MainViewModel @Inject constructor(
}
val content: LiveData<List<FavouriteContent>> = _content
private val _errorMessage = MutableLiveData<Boolean>(false)
val errorMessage: LiveData<Boolean> = _errorMessage
val errorMessage: LiveData<Boolean> = _errorMessage.distinctUntilChanged()
private val _navigateToAuth = MutableLiveData<Event<Unit>>()
val navigateToAuth: LiveData<Event<Unit>> = _navigateToAuth

View file

@ -6,6 +6,14 @@ import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
/**
* Junit5 Version of InstantTaskExecutorRule from Junit4
*
* reference: https://developer.android.com/reference/androidx/arch/core/executor/testing/InstantTaskExecutorRule
*
* A JUnit5 Extensions that swaps the background executor used by the Architecture Components with a different one which executes each task synchronously.
* You can use this extension for your host side tests that use Architecture Components.
*/
class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext?) {

View file

@ -5,10 +5,16 @@ import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.fnives.test.showcase.storage.database.DatabaseInitialization
import org.fnives.test.showcase.testutils.TestMainDispatcher.Companion.testDispatcher
import org.junit.jupiter.api.extension.AfterEachCallback
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
/**
* Custom Junit5 Extension which replaces the [DatabaseInitialization]'s dispatcher and main dispatcher with a [TestCoroutineDispatcher]
*
* One can access the test dispatcher via [testDispatcher] static getter.
*/
class TestMainDispatcher : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext?) {

View file

@ -39,7 +39,7 @@ internal class AuthViewModelTest {
sut = AuthViewModel(mockLoginUseCase)
}
@DisplayName("GIVEN_initialized_viewModel_WHEN_observed_THEN_loading_false_other_fields_are_empty")
@DisplayName("GIVEN initialized viewModel WHEN observed THEN loading false other fields are empty")
@Test
fun initialSetup() {
testDispatcher.resumeDispatcher()
@ -51,7 +51,7 @@ internal class AuthViewModelTest {
sut.navigateToHome.test().assertNoValue()
}
@DisplayName("GIVEN_password_text_WHEN_onPasswordChanged_is_called_THEN_password_livedata_is_updated")
@DisplayName("GIVEN password text WHEN onPasswordChanged is called THEN password livedata is updated")
@Test
fun whenPasswordChangedLiveDataIsUpdated() {
testDispatcher.resumeDispatcher()
@ -67,7 +67,7 @@ internal class AuthViewModelTest {
sut.navigateToHome.test().assertNoValue()
}
@DisplayName("GIVEN_username_text_WHEN_onUsernameChanged_is_called_THEN_username_livedata_is_updated")
@DisplayName("GIVEN username text WHEN onUsernameChanged is called THEN username livedata is updated")
@Test
fun whenUsernameChangedLiveDataIsUpdated() {
testDispatcher.resumeDispatcher()
@ -83,7 +83,7 @@ internal class AuthViewModelTest {
sut.navigateToHome.test().assertNoValue()
}
@DisplayName("GIVEN_no_password_or_username_WHEN_login_is_Called_THEN_empty_credentials_are_used_in_usecase")
@DisplayName("GIVEN no password or username WHEN login is Called THEN empty credentials are used in usecase")
@Test
fun noPasswordUsesEmptyStringInLoginUseCase() {
val loadingTestObserver = sut.loading.test()
@ -99,8 +99,9 @@ internal class AuthViewModelTest {
verifyNoMoreInteractions(mockLoginUseCase)
}
@DisplayName("WHEN login is called twice before finishing THEN use case is only called once")
@Test
fun WHEN_login_is_Called_twice_THEN_use_case_is_only_called_once() {
fun onlyOneLoginIsSentOutWhenClickingRepeatedly() {
runBlocking { whenever(mockLoginUseCase.invoke(anyOrNull())).doReturn(Answer.Error(Throwable())) }
sut.onLogin()
@ -111,8 +112,9 @@ internal class AuthViewModelTest {
verifyNoMoreInteractions(mockLoginUseCase)
}
@DisplayName("GIVEN password and username WHEN login is called THEN proper credentials are used in usecase")
@Test
fun GIVEN_password_and_username_WHEN_login_is_Called_THEN_not_empty_credentials_are_used_in_usecase() {
fun argumentsArePassedProperlyToLoginUseCase() {
runBlocking {
whenever(mockLoginUseCase.invoke(anyOrNull())).doReturn(Answer.Error(Throwable()))
}
@ -128,8 +130,9 @@ internal class AuthViewModelTest {
verifyNoMoreInteractions(mockLoginUseCase)
}
@DisplayName("GIVEN AnswerError WHEN login called THEN error is shown")
@Test
fun GIVEN_answer_error_WHEN_login_called_THEN_error_is_shown() {
fun loginErrorResultsInErrorState() {
runBlocking {
whenever(mockLoginUseCase.invoke(anyOrNull())).doReturn(Answer.Error(Throwable()))
}
@ -146,8 +149,8 @@ internal class AuthViewModelTest {
}
@MethodSource("loginErrorStatusesArguments")
@ParameterizedTest(name = "GIVEN_answer_success_loginStatus_{0}_WHEN_login_called_THEN_error_{1}_is_shown")
fun GIVEN_answer_success_invalid_loginStatus_WHEN_login_called_THEN_error_is_shown(
@ParameterizedTest(name = "GIVEN answer success loginStatus {0} WHEN login called THEN error {1} is shown")
fun invalidStatusResultsInErrorState(
loginStatus: LoginStatus,
errorType: AuthViewModel.ErrorType
) {
@ -166,8 +169,9 @@ internal class AuthViewModelTest {
navigateToHomeObserver.assertNoValue()
}
@DisplayName("GIVEN answer success and login status success WHEN login called THEN navigation event is sent")
@Test
fun GIVEN_answer_success_login_status_success_WHEN_login_called_THEN_navigation_event_is_sent() {
fun successLoginResultsInNavigation() {
runBlocking {
whenever(mockLoginUseCase.invoke(anyOrNull())).doReturn(Answer.Success(LoginStatus.SUCCESS))
}

View file

@ -24,22 +24,22 @@ class CodeKataAuthViewModel {
sut = AuthViewModel(mockLoginUseCase)
}
@DisplayName("GIVEN_initialized_viewModel_WHEN_observed_THEN_loading_false_other_fields_are_empty")
@DisplayName("GIVEN initialized viewModel WHEN observed THEN loading false other fields are empty")
@Test
fun initialSetup() {
}
@DisplayName("GIVEN_password_text_WHEN_onPasswordChanged_is_called_THEN_password_livedata_is_updated")
@DisplayName("GIVEN password text WHEN onPasswordChanged is called THEN password livedata is updated")
@Test
fun whenPasswordChangedLiveDataIsUpdated() {
}
@DisplayName("GIVEN_username_text_WHEN_onUsernameChanged_is_called_THEN_username_livedata_is_updated")
@DisplayName("GIVEN username text WHEN onUsernameChanged is called THEN username livedata is updated")
@Test
fun whenUsernameChangedLiveDataIsUpdated() {
}
@DisplayName("GIVEN_no_password_or_username_WHEN_login_is_Called_THEN_empty_credentials_are_used_in_usecase")
@DisplayName("GIVEN no password or username WHEN login is Called THEN empty credentials are used in usecase")
@Test
fun noPasswordUsesEmptyStringInLoginUseCase() {
}

View file

@ -16,6 +16,7 @@ import org.fnives.test.showcase.model.shared.Resource
import org.fnives.test.showcase.testutils.InstantExecutorExtension
import org.fnives.test.showcase.testutils.TestMainDispatcher
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.kotlin.doReturn
@ -55,16 +56,18 @@ internal class MainViewModelTest {
)
}
@DisplayName("WHEN initialization THEN error false other states empty")
@Test
fun WHEN_initialization_THEN_error_false_other_states_empty() {
fun initialStateIsCorrect() {
sut.errorMessage.test().assertValue(false)
sut.content.test().assertNoValue()
sut.loading.test().assertNoValue()
sut.navigateToAuth.test().assertNoValue()
}
@DisplayName("GIVEN initialized viewModel WHEN loading is returned THEN loading is shown")
@Test
fun GIVEN_initialized_viewModel_WHEN_loading_is_returned_THEN_loading_is_shown() {
fun loadingDataShowsInLoadingUIState() {
whenever(mockGetAllContentUseCase.get()).doReturn(flowOf(Resource.Loading()))
testDispatcher.resumeDispatcher()
testDispatcher.advanceUntilIdle()
@ -75,8 +78,9 @@ internal class MainViewModelTest {
sut.navigateToAuth.test().assertNoValue()
}
@DisplayName("GIVEN loading then data WHEN observing content THEN proper states are shown")
@Test
fun GIVEN_loading_then_data_WHEN_observing_content_THEN_proper_states_are_shown() {
fun loadingThenLoadedDataResultsInProperUIStates() {
whenever(mockGetAllContentUseCase.get()).doReturn(flowOf(Resource.Loading(), Resource.Success(emptyList())))
val errorMessageTestObserver = sut.errorMessage.test()
val contentTestObserver = sut.content.test()
@ -84,14 +88,15 @@ internal class MainViewModelTest {
testDispatcher.resumeDispatcher()
testDispatcher.advanceUntilIdle()
errorMessageTestObserver.assertValueHistory(false, false, false)
errorMessageTestObserver.assertValueHistory(false)
contentTestObserver.assertValueHistory(listOf())
loadingTestObserver.assertValueHistory(true, false)
sut.navigateToAuth.test().assertNoValue()
}
@DisplayName("GIVEN loading then error WHEN observing content THEN proper states are shown")
@Test
fun GIVEN_loading_then_error_WHEN_observing_content_THEN_proper_states_are_shown() {
fun loadingThenErrorResultsInProperUIStates() {
whenever(mockGetAllContentUseCase.get()).doReturn(flowOf(Resource.Loading(), Resource.Error(Throwable())))
val errorMessageTestObserver = sut.errorMessage.test()
val contentTestObserver = sut.content.test()
@ -99,14 +104,15 @@ internal class MainViewModelTest {
testDispatcher.resumeDispatcher()
testDispatcher.advanceUntilIdle()
errorMessageTestObserver.assertValueHistory(false, false, true)
errorMessageTestObserver.assertValueHistory(false, true)
contentTestObserver.assertValueHistory(emptyList())
loadingTestObserver.assertValueHistory(true, false)
sut.navigateToAuth.test().assertNoValue()
}
@DisplayName("GIVEN loading then error then loading then data WHEN observing content THEN proper states are shown")
@Test
fun GIVEN_loading_then_error_then_loading_then_data_WHEN_observing_content_THEN_proper_states_are_shown() {
fun loadingThenErrorThenLoadingThenDataResultsInProperUIStates() {
val content = listOf(
FavouriteContent(Content(ContentId(""), "", "", ImageUrl("")), false)
)
@ -124,14 +130,15 @@ internal class MainViewModelTest {
testDispatcher.resumeDispatcher()
testDispatcher.advanceUntilIdle()
errorMessageTestObserver.assertValueHistory(false, false, true, false, false)
errorMessageTestObserver.assertValueHistory(false, true, false)
contentTestObserver.assertValueHistory(emptyList(), content)
loadingTestObserver.assertValueHistory(true, false, true, false)
sut.navigateToAuth.test().assertNoValue()
}
@DisplayName("GIVEN loading viewModel WHEN refreshing THEN usecase is not called")
@Test
fun GIVEN_loading_viewModel_WHEN_refreshing_THEN_usecase_is_not_called() {
fun fetchIsIgnoredIfViewModelIsStillLoading() {
whenever(mockGetAllContentUseCase.get()).doReturn(flowOf(Resource.Loading()))
sut.content.test()
testDispatcher.resumeDispatcher()
@ -143,8 +150,9 @@ internal class MainViewModelTest {
verifyZeroInteractions(mockFetchContentUseCase)
}
@DisplayName("GIVEN non loading viewModel WHEN refreshing THEN usecase is called")
@Test
fun GIVEN_non_loading_viewModel_WHEN_refreshing_THEN_usecase_is_called() {
fun fetchIsCalledIfViewModelIsLoaded() {
whenever(mockGetAllContentUseCase.get()).doReturn(flowOf())
sut.content.test()
testDispatcher.resumeDispatcher()
@ -157,8 +165,9 @@ internal class MainViewModelTest {
verifyNoMoreInteractions(mockFetchContentUseCase)
}
@DisplayName("GIVEN loading viewModel WHEN loging out THEN usecase is called")
@Test
fun GIVEN_loading_viewModel_WHEN_loging_out_THEN_usecase_is_called() {
fun loadingViewModelStillCalsLogout() {
whenever(mockGetAllContentUseCase.get()).doReturn(flowOf(Resource.Loading()))
sut.content.test()
testDispatcher.resumeDispatcher()
@ -171,8 +180,9 @@ internal class MainViewModelTest {
verifyNoMoreInteractions(mockLogoutUseCase)
}
@DisplayName("GIVEN non loading viewModel WHEN loging out THEN usecase is called")
@Test
fun GIVEN_non_loading_viewModel_WHEN_loging_out_THEN_usecase_is_called() {
fun nonLoadingViewModelStillCalsLogout() {
whenever(mockGetAllContentUseCase.get()).doReturn(flowOf())
sut.content.test()
testDispatcher.resumeDispatcher()
@ -185,8 +195,9 @@ internal class MainViewModelTest {
verifyNoMoreInteractions(mockLogoutUseCase)
}
@DisplayName("GIVEN success content list viewModel WHEN toggling a nonexistent contentId THEN nothing happens")
@Test
fun GIVEN_success_content_list_viewModel_WHEN_toggling_a_nonexistent_contentId_THEN_nothing_happens() {
fun interactionWithNonExistentContentIdIsIgnored() {
val contents = listOf(
FavouriteContent(Content(ContentId("a"), "", "", ImageUrl("")), false),
FavouriteContent(Content(ContentId("b"), "", "", ImageUrl("")), true)
@ -203,8 +214,9 @@ internal class MainViewModelTest {
verifyZeroInteractions(mockAddContentToFavouriteUseCase)
}
@DisplayName("GIVEN success content list viewModel WHEN toggling a favourite contentId THEN remove favourite usecase is called")
@Test
fun GIVEN_success_content_list_viewModel_WHEN_toggling_a_favourite_contentId_THEN_remove_favourite_usecase_is_called() {
fun togglingFavouriteContentCallsRemoveFromFavourite() {
val contents = listOf(
FavouriteContent(Content(ContentId("a"), "", "", ImageUrl("")), false),
FavouriteContent(Content(ContentId("b"), "", "", ImageUrl("")), true)
@ -222,8 +234,9 @@ internal class MainViewModelTest {
verifyZeroInteractions(mockAddContentToFavouriteUseCase)
}
@DisplayName("GIVEN success content list viewModel WHEN toggling a not favourite contentId THEN add favourite usecase is called")
@Test
fun GIVEN_success_content_list_viewModel_WHEN_toggling_a_not_favourite_contentId_THEN_add_favourite_usecase_is_called() {
fun togglingNonFavouriteContentCallsAddToFavourite() {
val contents = listOf(
FavouriteContent(Content(ContentId("a"), "", "", ImageUrl("")), false),
FavouriteContent(Content(ContentId("b"), "", "", ImageUrl("")), true)

View file

@ -1,13 +1,15 @@
package org.fnives.test.showcase.ui.shared
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
@Suppress("TestFunctionName")
internal class EventTest {
@DisplayName("GIVEN event WHEN consumed is called THEN value is returned")
@Test
fun GIVEN_event_WHEN_consumed_is_called_THEN_value_is_returned() {
fun consumedReturnsValue() {
val expected = "a"
val actual = Event("a").consume()
@ -15,8 +17,9 @@ internal class EventTest {
Assertions.assertEquals(expected, actual)
}
@DisplayName("GIVEN consumed event WHEN consumed is called THEN null is returned")
@Test
fun GIVEN_consumed_event_WHEN_consumed_is_called_THEN_null_is_returned() {
fun consumedEventReturnsNull() {
val expected: String? = null
val event = Event("a")
event.consume()
@ -26,8 +29,9 @@ internal class EventTest {
Assertions.assertEquals(expected, actual)
}
@DisplayName("GIVEN event WHEN peek is called THEN value is returned")
@Test
fun GIVEN_event_WHEN_peek_is_called_THEN_value_is_returned() {
fun peekReturnsValue() {
val expected = "a"
val actual = Event("a").peek()
@ -35,8 +39,9 @@ internal class EventTest {
Assertions.assertEquals(expected, actual)
}
@DisplayName("GIVEN consumed event WHEN peek is called THEN value is returned")
@Test
fun GIVEN_consumed_event_WHEN_peek_is_called_THEN_value_is_returned() {
fun consumedEventPeekedReturnsValue() {
val expected = "a"
val event = Event("a")
event.consume()