From c9d4223ce60c38a34672dd16241e52457f0860bc Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Thu, 4 Nov 2021 19:42:57 +0200 Subject: [PATCH 1/7] issue#6 Add DisplayName annotation to all tests in network module --- .../network/auth/LoginErrorConverterTest.kt | 13 ++++-- .../LoginRemoteSourceRefreshActionImplTest.kt | 41 +++++++++++-------- .../LoginRemoteSourceRefreshActionImplTest.kt | 16 +++++--- .../hilt/ContentRemoteSourceImplTest.kt | 19 ++++++--- .../koin/ContentRemoteSourceImplTest.kt | 19 ++++++--- 5 files changed, 69 insertions(+), 39 deletions(-) diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginErrorConverterTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/LoginErrorConverterTest.kt index 6dc87fd..d066313 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/LoginErrorConverterTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/LoginErrorConverterTest.kt @@ -12,6 +12,7 @@ import org.fnives.test.showcase.network.shared.exceptions.NetworkException import org.fnives.test.showcase.network.shared.exceptions.ParsingException import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import retrofit2.Response import java.io.IOException @@ -26,8 +27,9 @@ class LoginErrorConverterTest { sut = LoginErrorConverter() } + @DisplayName("GIVEN throwing lambda WHEN parsing login error THEN network exception is thrown") @Test - fun GIVEN_throwing_lambda_WHEN_parsing_login_error_THEN_network_exception_is_thrown() { + fun generallyThrowingLambdaResultsInNetworkException() { Assertions.assertThrows(NetworkException::class.java) { runBlocking { sut.invoke { throw IOException() } @@ -35,8 +37,9 @@ class LoginErrorConverterTest { } } + @DisplayName("GIVEN jsonException throwing lambda WHEN parsing login error THEN network exception is thrown") @Test - fun GIVEN_jsonException_throwing_lambda_WHEN_parsing_login_error_THEN_network_exception_is_thrown() { + fun jsonDataThrowingLambdaResultsInParsingException() { Assertions.assertThrows(ParsingException::class.java) { runBlocking { sut.invoke { throw JsonDataException("") } @@ -44,8 +47,9 @@ class LoginErrorConverterTest { } } + @DisplayName("GIVEN 400 error response WHEN parsing login error THEN invalid credentials is returned") @Test - fun GIVEN_400_error_response_WHEN_parsing_login_error_THEN_invalid_credentials_is_returned() = runBlockingTest { + fun code400ResponseResultsInInvalidCredentials() = runBlockingTest { val expected = LoginStatusResponses.InvalidCredentials val actual = sut.invoke { @@ -56,8 +60,9 @@ class LoginErrorConverterTest { Assertions.assertEquals(expected, actual) } + @DisplayName("GIVEN successful response WHEN parsing login error THEN successful response is returned") @Test - fun GIVEN_successful_response_WHEN_parsing_login_error_THEN_successful_response_is_returned() = runBlockingTest { + fun successResponseResultsInSessionResponse() = runBlockingTest { val loginResponse = LoginResponse("a", "r") val expectedSession = Session(accessToken = loginResponse.accessToken, refreshToken = loginResponse.refreshToken) val expected = LoginStatusResponses.Success(expectedSession) diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt index 1b9e1f1..5490cb0 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt @@ -11,6 +11,7 @@ import org.fnives.test.showcase.network.shared.exceptions.NetworkException import org.fnives.test.showcase.network.shared.exceptions.ParsingException import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension import org.mockito.kotlin.mock @@ -41,8 +42,9 @@ class LoginRemoteSourceRefreshActionImplTest { .inject(this) } + @DisplayName("GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_session_is_returned") @Test - fun GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_session() = runBlocking { + fun successResponseResultsInSession() = runBlocking { mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success) val expected = ContentData.refreshSuccessResponse @@ -51,26 +53,27 @@ class LoginRemoteSourceRefreshActionImplTest { Assertions.assertEquals(expected, actual) } + @DisplayName("GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_the_request_is_setup_properly") @Test - fun GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_the_request_is_setup_properly() = - runBlocking { - mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false) + fun refreshRequestIsSetupProperly() = runBlocking { + mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false) - sut.refresh(ContentData.refreshSuccessResponse.refreshToken) - val request = mockServerScenarioSetup.takeRequest() + sut.refresh(ContentData.refreshSuccessResponse.refreshToken) + val request = mockServerScenarioSetup.takeRequest() - Assertions.assertEquals("PUT", request.method) - Assertions.assertEquals("Android", request.getHeader("Platform")) - Assertions.assertEquals(null, request.getHeader("Authorization")) - Assertions.assertEquals( - "/login/${ContentData.refreshSuccessResponse.refreshToken}", - request.path - ) - Assertions.assertEquals("", request.body.readUtf8()) - } + Assertions.assertEquals("PUT", request.method) + Assertions.assertEquals("Android", request.getHeader("Platform")) + Assertions.assertEquals(null, request.getHeader("Authorization")) + Assertions.assertEquals( + "/login/${ContentData.refreshSuccessResponse.refreshToken}", + request.path + ) + Assertions.assertEquals("", request.body.readUtf8()) + } + @DisplayName("GIVEN_internal_error_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown") @Test - fun GIVEN_internal_error_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown() { + fun generalErrorResponseResultsInNetworkException() { mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error) Assertions.assertThrows(NetworkException::class.java) { @@ -78,8 +81,9 @@ class LoginRemoteSourceRefreshActionImplTest { } } + @DisplayName("GIVEN_invalid_json_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown") @Test - fun GIVEN_invalid_json_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown() { + fun jsonErrorResponseResultsInParsingException() { mockServerScenarioSetup.setScenario(RefreshTokenScenario.UnexpectedJsonAsSuccessResponse) Assertions.assertThrows(ParsingException::class.java) { @@ -87,8 +91,9 @@ class LoginRemoteSourceRefreshActionImplTest { } } + @DisplayName("GIVEN_malformed_json_response_WHEN_refresh_request_is_fired_THEN_parsing_exception_is_thrown") @Test - fun GIVEN_malformed_json_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown() { + fun malformedJsonErrorResponseResultsInParsingException() { mockServerScenarioSetup.setScenario(RefreshTokenScenario.MalformedJson) Assertions.assertThrows(ParsingException::class.java) { diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt index cbf9ae7..80a2a77 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt @@ -13,6 +13,7 @@ import org.fnives.test.showcase.network.shared.exceptions.ParsingException import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension import org.koin.core.context.startKoin @@ -53,8 +54,9 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest { stopKoin() } + @DisplayName("GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_session_is_returned") @Test - fun GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_session() = runBlocking { + fun successResponseResultsInSession() = runBlocking { mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success) val expected = ContentData.refreshSuccessResponse @@ -63,8 +65,9 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest { Assertions.assertEquals(expected, actual) } + @DisplayName("GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_the_request_is_setup_properly") @Test - fun GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_the_request_is_setup_properly() = runBlocking { + fun refreshRequestIsSetupProperly() = runBlocking { mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false) sut.refresh(ContentData.refreshSuccessResponse.refreshToken) @@ -77,8 +80,9 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest { Assertions.assertEquals("", request.body.readUtf8()) } + @DisplayName("GIVEN_internal_error_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown") @Test - fun GIVEN_internal_error_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown() { + fun generalErrorResponseResultsInNetworkException() { mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error) Assertions.assertThrows(NetworkException::class.java) { @@ -86,8 +90,9 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest { } } + @DisplayName("GIVEN_invalid_json_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown") @Test - fun GIVEN_invalid_json_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown() { + fun jsonErrorResponseResultsInParsingException() { mockServerScenarioSetup.setScenario(RefreshTokenScenario.UnexpectedJsonAsSuccessResponse) Assertions.assertThrows(ParsingException::class.java) { @@ -95,8 +100,9 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest { } } + @DisplayName("GIVEN_malformed_json_response_WHEN_refresh_request_is_fired_THEN_parsing_exception_is_thrown") @Test - fun GIVEN_malformed_json_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown() { + fun malformedJsonErrorResponseResultsInParsingException() { mockServerScenarioSetup.setScenario(RefreshTokenScenario.MalformedJson) Assertions.assertThrows(ParsingException::class.java) { diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt index d1b669d..0b4c0c0 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/ContentRemoteSourceImplTest.kt @@ -11,6 +11,7 @@ import org.fnives.test.showcase.network.shared.exceptions.NetworkException import org.fnives.test.showcase.network.shared.exceptions.ParsingException import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension import org.koin.test.inject @@ -44,8 +45,9 @@ class ContentRemoteSourceImplTest { .inject(this) } + @DisplayName("GIVEN successful response WHEN getting content THEN its parsed and returned correctly") @Test - fun GIVEN_successful_response_WHEN_getting_content_THEN_its_parsed_and_returned_correctly() = runBlocking { + fun successResponseParsing() = runBlocking { whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) mockServerScenarioSetup.setScenario(ContentScenario.Success(false)) val expected = ContentData.contentSuccess @@ -55,8 +57,9 @@ class ContentRemoteSourceImplTest { Assertions.assertEquals(expected, actual) } + @DisplayName("GIVEN successful response WHEN getting content THEN the request is setup properly") @Test - fun GIVEN_successful_response_WHEN_getting_content_THEN_the_request_is_setup_properly() = runBlocking { + fun successResponseRequestIsCorrect() = runBlocking { whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) mockServerScenarioSetup.setScenario(ContentScenario.Success(false), false) @@ -70,8 +73,9 @@ class ContentRemoteSourceImplTest { Assertions.assertEquals("", request.body.readUtf8()) } + @DisplayName("GIVEN response with missing Field WHEN getting content THEN invalid is ignored others are returned") @Test - fun GIVEN_response_with_missing_Field_WHEN_getting_content_THEN_invalid_is_ignored_others_are_returned() = runBlocking { + fun dataMissingFieldIsIgnored() = runBlocking { whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) mockServerScenarioSetup.setScenario(ContentScenario.SuccessWithMissingFields(false)) @@ -82,8 +86,9 @@ class ContentRemoteSourceImplTest { Assertions.assertEquals(expected, actual) } + @DisplayName("GIVEN error response WHEN getting content THEN network request is thrown") @Test - fun GIVEN_error_response_WHEN_getting_content_THEN_network_request_is_thrown() { + fun errorResponseResultsInNetworkException() { whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) mockServerScenarioSetup.setScenario(ContentScenario.Error(false)) @@ -92,8 +97,9 @@ class ContentRemoteSourceImplTest { } } + @DisplayName("GIVEN unexpected json response WHEN getting content THEN parsing request is thrown") @Test - fun GIVEN_unexpected_json_response_WHEN_getting_content_THEN_parsing_request_is_thrown() { + fun unexpectedJSONResultsInParsingException() { whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) mockServerScenarioSetup.setScenario(ContentScenario.UnexpectedJsonAsSuccessResponse(false)) @@ -102,8 +108,9 @@ class ContentRemoteSourceImplTest { } } + @DisplayName("GIVEN malformed json response WHEN getting content THEN parsing request is thrown") @Test - fun GIVEN_malformed_json_response_WHEN_getting_content_THEN_parsing_request_is_thrown() { + fun malformedJSONResultsInParsingException() { whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) mockServerScenarioSetup.setScenario(ContentScenario.MalformedJsonAsSuccessResponse(false)) diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/koin/ContentRemoteSourceImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/koin/ContentRemoteSourceImplTest.kt index 6edc095..400aba6 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/koin/ContentRemoteSourceImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/koin/ContentRemoteSourceImplTest.kt @@ -13,6 +13,7 @@ import org.fnives.test.showcase.network.shared.exceptions.ParsingException import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension import org.koin.core.context.startKoin @@ -55,8 +56,9 @@ class ContentRemoteSourceImplTest : KoinTest { stopKoin() } + @DisplayName("GIVEN successful response WHEN getting content THEN its parsed and returned correctly") @Test - fun GIVEN_successful_response_WHEN_getting_content_THEN_its_parsed_and_returned_correctly() = runBlocking { + fun successResponseParsing() = runBlocking { whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) mockServerScenarioSetup.setScenario(ContentScenario.Success(false)) val expected = ContentData.contentSuccess @@ -66,8 +68,9 @@ class ContentRemoteSourceImplTest : KoinTest { Assertions.assertEquals(expected, actual) } + @DisplayName("GIVEN successful response WHEN getting content THEN the request is setup properly") @Test - fun GIVEN_successful_response_WHEN_getting_content_THEN_the_request_is_setup_properly() = runBlocking { + fun successResponseRequestIsCorrect() = runBlocking { whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) mockServerScenarioSetup.setScenario(ContentScenario.Success(false), false) @@ -81,8 +84,9 @@ class ContentRemoteSourceImplTest : KoinTest { Assertions.assertEquals("", request.body.readUtf8()) } + @DisplayName("GIVEN response with missing Field WHEN getting content THEN invalid is ignored others are returned") @Test - fun GIVEN_response_with_missing_Field_WHEN_getting_content_THEN_invalid_is_ignored_others_are_returned() = runBlocking { + fun dataMissingFieldIsIgnored() = runBlocking { whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) mockServerScenarioSetup.setScenario(ContentScenario.SuccessWithMissingFields(false)) @@ -93,8 +97,9 @@ class ContentRemoteSourceImplTest : KoinTest { Assertions.assertEquals(expected, actual) } + @DisplayName("GIVEN error response WHEN getting content THEN network request is thrown") @Test - fun GIVEN_error_response_WHEN_getting_content_THEN_network_request_is_thrown() { + fun errorResponseResultsInNetworkException() { whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) mockServerScenarioSetup.setScenario(ContentScenario.Error(false)) @@ -103,8 +108,9 @@ class ContentRemoteSourceImplTest : KoinTest { } } + @DisplayName("GIVEN unexpected json response WHEN getting content THEN parsing request is thrown") @Test - fun GIVEN_unexpected_json_response_WHEN_getting_content_THEN_parsing_request_is_thrown() { + fun unexpectedJSONResultsInParsingException() { whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) mockServerScenarioSetup.setScenario(ContentScenario.UnexpectedJsonAsSuccessResponse(false)) @@ -113,8 +119,9 @@ class ContentRemoteSourceImplTest : KoinTest { } } + @DisplayName("GIVEN malformed json response WHEN getting content THEN parsing request is thrown") @Test - fun GIVEN_malformed_json_response_WHEN_getting_content_THEN_parsing_request_is_thrown() { + fun malformedJSONResultsInParsingException() { whenever(mockNetworkSessionLocalStorage.session).doReturn(ContentData.loginSuccessResponse) mockServerScenarioSetup.setScenario(ContentScenario.MalformedJsonAsSuccessResponse(false)) From e7f840a1b9f2db1e10b849c9d76481b7b82ed5e9 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Thu, 4 Nov 2021 20:04:01 +0200 Subject: [PATCH 2/7] issue#6 Add DisplayName annotation to all tests in core module --- .../AddContentToFavouriteUseCaseTest.kt | 14 ++++++--- .../core/content/FetchContentUseCaseTest.kt | 10 ++++-- .../core/content/GetAllContentUseCaseTest.kt | 31 +++++++++++++------ .../RemoveContentFromFavouritesUseCaseTest.kt | 10 ++++-- .../core/login/IsUserLoggedInUseCaseTest.kt | 13 +++++--- .../core/login/hilt/LogoutUseCaseTest.kt | 7 +++-- .../core/login/koin/LogoutUseCaseTest.kt | 7 +++-- .../showcase/core/shared/AnswerUtilsKtTest.kt | 22 ++++++++----- .../NetworkSessionLocalStorageAdapterTest.kt | 10 ++++-- 9 files changed, 86 insertions(+), 38 deletions(-) diff --git a/core/src/test/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCaseTest.kt index f146004..e930064 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCaseTest.kt @@ -6,6 +6,7 @@ import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorag import org.fnives.test.showcase.model.content.ContentId import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.kotlin.doThrow import org.mockito.kotlin.mock @@ -27,22 +28,27 @@ internal class AddContentToFavouriteUseCaseTest { sut = AddContentToFavouriteUseCase(mockFavouriteContentLocalStorage) } + @DisplayName("WHEN_nothing_happens_THEN_the_storage_is_not_touched") @Test - fun WHEN_nothing_happens_THEN_the_storage_is_not_touched() { + fun initializationDoesntAffectStorage() { verifyZeroInteractions(mockFavouriteContentLocalStorage) } + @DisplayName("GIVEN_contentId_WHEN_called_THEN_storage_is_called") @Test - fun GIVEN_contentId_WHEN_called_THEN_storage_is_called() = runBlockingTest { + fun contentIdIsDelegatedToStorage() = runBlockingTest { sut.invoke(ContentId("a")) verify(mockFavouriteContentLocalStorage, times(1)).markAsFavourite(ContentId("a")) verifyNoMoreInteractions(mockFavouriteContentLocalStorage) } + @DisplayName("GIVEN_throwing_local_storage_WHEN_thrown_THEN_its_propagated") @Test - fun GIVEN_throwing_local_storage_WHEN_thrown_THEN_its_thrown() = runBlockingTest { - whenever(mockFavouriteContentLocalStorage.markAsFavourite(ContentId("a"))).doThrow(RuntimeException()) + fun storageThrowingIsPropagated() = runBlockingTest { + whenever(mockFavouriteContentLocalStorage.markAsFavourite(ContentId("a"))).doThrow( + RuntimeException() + ) assertThrows(RuntimeException::class.java) { runBlocking { sut.invoke(ContentId("a")) } diff --git a/core/src/test/java/org/fnives/test/showcase/core/content/FetchContentUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/content/FetchContentUseCaseTest.kt index 8256cfd..2490ec1 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/content/FetchContentUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/content/FetchContentUseCaseTest.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runBlockingTest import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.kotlin.doThrow import org.mockito.kotlin.mock @@ -25,21 +26,24 @@ internal class FetchContentUseCaseTest { sut = FetchContentUseCase(mockContentRepository) } + @DisplayName("WHEN nothing happens THEN the storage is not touched") @Test - fun WHEN_nothing_happens_THEN_the_storage_is_not_touched() { + fun initializationDoesntAffectRepository() { verifyZeroInteractions(mockContentRepository) } + @DisplayName("WHEN called THEN repository is called") @Test - fun WHEN_called_THEN_repository_is_called() = runBlockingTest { + fun whenCalledRepositoryIsFetched() = runBlockingTest { sut.invoke() verify(mockContentRepository, times(1)).fetch() verifyNoMoreInteractions(mockContentRepository) } + @DisplayName("GIVEN throwing local storage WHEN thrown THEN its thrown") @Test - fun GIVEN_throwing_local_storage_WHEN_thrown_THEN_its_thrown() = runBlockingTest { + fun whenRepositoryThrowsUseCaseAlsoThrows() = runBlockingTest { whenever(mockContentRepository.fetch()).doThrow(RuntimeException()) assertThrows(RuntimeException::class.java) { diff --git a/core/src/test/java/org/fnives/test/showcase/core/content/GetAllContentUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/content/GetAllContentUseCaseTest.kt index 5e36cd8..c673592 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/content/GetAllContentUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/content/GetAllContentUseCaseTest.kt @@ -15,6 +15,7 @@ import org.fnives.test.showcase.model.content.ImageUrl import org.fnives.test.showcase.model.shared.Resource import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock @@ -45,8 +46,9 @@ internal class GetAllContentUseCaseTest { sut = GetAllContentUseCase(mockContentRepository, mockFavouriteContentLocalStorage) } + @DisplayName("GIVEN loading AND empty favourite WHEN observed THEN loading is shown") @Test - fun GIVEN_loading_AND_empty_favourite_WHEN_observed_THEN_loading_is_shown() = + fun loadingResourceWithNoFavouritesResultsInLoadingResource() = runBlockingTest(testDispatcher) { favouriteContentIdFlow.value = emptyList() contentFlow.value = Resource.Loading() @@ -57,8 +59,9 @@ internal class GetAllContentUseCaseTest { Assertions.assertEquals(listOf(expected), actual) } + @DisplayName("GIVEN loading AND listOfFavourite WHEN observed THEN loading is shown") @Test - fun GIVEN_loading_AND_listOfFavourite_WHEN_observed_THEN_loading_is_shown() = + fun loadingResourceWithFavouritesResultsInLoadingResource() = runBlockingTest(testDispatcher) { favouriteContentIdFlow.value = listOf(ContentId("a")) contentFlow.value = Resource.Loading() @@ -69,8 +72,9 @@ internal class GetAllContentUseCaseTest { Assertions.assertEquals(listOf(expected), actual) } + @DisplayName("GIVEN error AND empty favourite WHEN observed THEN error is shown") @Test - fun GIVEN_error_AND_empty_favourite_WHEN_observed_THEN_error_is_shown() = + fun errorResourceWithNoFavouritesResultsInErrorResource() = runBlockingTest(testDispatcher) { favouriteContentIdFlow.value = emptyList() val exception = Throwable() @@ -82,8 +86,9 @@ internal class GetAllContentUseCaseTest { Assertions.assertEquals(listOf(expected), actual) } + @DisplayName("GIVEN error AND listOfFavourite WHEN observed THEN error is shown") @Test - fun GIVEN_error_AND_listOfFavourite_WHEN_observed_THEN_error_is_shown() = + fun errorResourceWithFavouritesResultsInErrorResource() = runBlockingTest(testDispatcher) { favouriteContentIdFlow.value = listOf(ContentId("b")) val exception = Throwable() @@ -95,8 +100,9 @@ internal class GetAllContentUseCaseTest { Assertions.assertEquals(listOf(expected), actual) } + @DisplayName("GIVEN listOfContent AND empty favourite WHEN observed THEN favourites are returned") @Test - fun GIVEN_listOfContent_AND_empty_favourite_WHEN_observed_THEN_favourites_are_returned() = + fun successResourceWithNoFavouritesResultsInNoFavouritedItems() = runBlockingTest(testDispatcher) { favouriteContentIdFlow.value = emptyList() val content = Content(ContentId("a"), "b", "c", ImageUrl("d")) @@ -111,8 +117,9 @@ internal class GetAllContentUseCaseTest { Assertions.assertEquals(listOf(expected), actual) } + @DisplayName("GIVEN listOfContent AND other favourite id WHEN observed THEN favourites are returned") @Test - fun GIVEN_listOfContent_AND_other_favourite_id_WHEN_observed_THEN_favourites_are_returned() = + fun successResourceWithDifferentFavouritesResultsInNoFavouritedItems() = runBlockingTest(testDispatcher) { favouriteContentIdFlow.value = listOf(ContentId("x")) val content = Content(ContentId("a"), "b", "c", ImageUrl("d")) @@ -127,8 +134,9 @@ internal class GetAllContentUseCaseTest { Assertions.assertEquals(listOf(expected), actual) } + @DisplayName("GIVEN listOfContent AND same favourite id WHEN observed THEN favourites are returned") @Test - fun GIVEN_listOfContent_AND_same_favourite_id_WHEN_observed_THEN_favourites_are_returned() = + fun successResourceWithSameFavouritesResultsInFavouritedItems() = runBlockingTest(testDispatcher) { favouriteContentIdFlow.value = listOf(ContentId("a")) val content = Content(ContentId("a"), "b", "c", ImageUrl("d")) @@ -143,8 +151,9 @@ internal class GetAllContentUseCaseTest { Assertions.assertEquals(listOf(expected), actual) } + @DisplayName("GIVEN loading then data then added favourite WHEN observed THEN loading then correct favourites are returned") @Test - fun GIVEN_loading_then_data_then_added_favourite_WHEN_observed_THEN_loading_then_correct_favourites_are_returned() = + fun whileLoadingAndAddingItemsReactsProperly() = runBlockingTest(testDispatcher) { favouriteContentIdFlow.value = emptyList() val content = Content(ContentId("a"), "b", "c", ImageUrl("d")) @@ -169,8 +178,9 @@ internal class GetAllContentUseCaseTest { Assertions.assertEquals(expected, actual.await()) } + @DisplayName("GIVEN loading then data then removed favourite WHEN observed THEN loading then correct favourites are returned") @Test - fun GIVEN_loading_then_data_then_removed_favourite_WHEN_observed_THEN_loading_then_correct_favourites_are_returned() = + fun whileLoadingAndRemovingItemsReactsProperly() = runBlockingTest(testDispatcher) { favouriteContentIdFlow.value = listOf(ContentId("a")) val content = Content(ContentId("a"), "b", "c", ImageUrl("d")) @@ -195,8 +205,9 @@ internal class GetAllContentUseCaseTest { Assertions.assertEquals(expected, actual.await()) } + @DisplayName("GIVEN loading then data then loading WHEN observed THEN loading then correct favourites then loading are returned") @Test - fun GIVEN_loading_then_data_then_loading_WHEN_observed_THEN_loading_then_correct_favourites_then_loadingare_returned() = + fun loadingThenDataThenLoadingReactsProperly() = runBlockingTest(testDispatcher) { favouriteContentIdFlow.value = listOf(ContentId("a")) val content = Content(ContentId("a"), "b", "c", ImageUrl("d")) diff --git a/core/src/test/java/org/fnives/test/showcase/core/content/RemoveContentFromFavouritesUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/content/RemoveContentFromFavouritesUseCaseTest.kt index c3d18da..3b896ce 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/content/RemoveContentFromFavouritesUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/content/RemoveContentFromFavouritesUseCaseTest.kt @@ -6,6 +6,7 @@ import org.fnives.test.showcase.core.storage.content.FavouriteContentLocalStorag import org.fnives.test.showcase.model.content.ContentId import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.kotlin.doThrow import org.mockito.kotlin.mock @@ -27,21 +28,24 @@ internal class RemoveContentFromFavouritesUseCaseTest { sut = RemoveContentFromFavouritesUseCase(mockFavouriteContentLocalStorage) } + @DisplayName("WHEN nothing happens THEN the storage is not touched") @Test - fun WHEN_nothing_happens_THEN_the_storage_is_not_touched() { + fun initializationDoesntAffectStorage() { verifyZeroInteractions(mockFavouriteContentLocalStorage) } + @DisplayName("GIVEN contentId WHEN called THEN storage is called") @Test - fun GIVEN_contentId_WHEN_called_THEN_storage_is_called() = runBlockingTest { + fun givenContentIdCallsStorage() = runBlockingTest { sut.invoke(ContentId("a")) verify(mockFavouriteContentLocalStorage, times(1)).deleteAsFavourite(ContentId("a")) verifyNoMoreInteractions(mockFavouriteContentLocalStorage) } + @DisplayName("GIVEN throwing local storage WHEN thrown THEN its propogated") @Test - fun GIVEN_throwing_local_storage_WHEN_thrown_THEN_its_thrown() = runBlockingTest { + fun storageExceptionThrowingIsPropogated() = runBlockingTest { whenever(mockFavouriteContentLocalStorage.deleteAsFavourite(ContentId("a"))).doThrow(RuntimeException()) Assertions.assertThrows(RuntimeException::class.java) { diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/IsUserLoggedInUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/login/IsUserLoggedInUseCaseTest.kt index 3b9d0a0..1fbaaa2 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/IsUserLoggedInUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/IsUserLoggedInUseCaseTest.kt @@ -4,6 +4,7 @@ import org.fnives.test.showcase.core.storage.UserDataLocalStorage import org.fnives.test.showcase.model.session.Session import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock @@ -22,13 +23,15 @@ internal class IsUserLoggedInUseCaseTest { sut = IsUserLoggedInUseCase(mockUserDataLocalStorage) } + @DisplayName("WHEN nothing is called THEN storage is not called") @Test - fun WHEN_nothing_is_called_THEN_storage_is_not_called() { + fun creatingDoesntAffectStorage() { verifyZeroInteractions(mockUserDataLocalStorage) } + @DisplayName("GIVEN session data saved WHEN is user logged in checked THEN true is returned") @Test - fun GIVEN_session_data_saved_WHEN_is_user_logged_in_checked_THEN_true_is_returned() { + fun sessionInStorageResultsInLoggedIn() { whenever(mockUserDataLocalStorage.session).doReturn(Session("a", "b")) val actual = sut.invoke() @@ -36,8 +39,9 @@ internal class IsUserLoggedInUseCaseTest { Assertions.assertEquals(true, actual) } + @DisplayName("GIVEN no session data saved WHEN is user logged in checked THEN false is returned") @Test - fun GIVEN_no_session_data_saved_WHEN_is_user_logged_in_checked_THEN_false_is_returned() { + fun noSessionInStorageResultsInLoggedOut() { whenever(mockUserDataLocalStorage.session).doReturn(null) val actual = sut.invoke() @@ -45,8 +49,9 @@ internal class IsUserLoggedInUseCaseTest { Assertions.assertEquals(false, actual) } + @DisplayName("GIVEN no session THEN session THEN no session WHEN is user logged in checked over again THEN every return is correct") @Test - fun GIVEN_no_session_THEN_session_THEN_no_session_WHEN_is_user_logged_in_checked_over_again_THEN_every_return_is_correct() { + fun multipleSessionSettingsResultsInCorrectResponses() { whenever(mockUserDataLocalStorage.session).doReturn(null) val actual1 = sut.invoke() whenever(mockUserDataLocalStorage.session).doReturn(Session("", "")) diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/hilt/LogoutUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/login/hilt/LogoutUseCaseTest.kt index 20ea451..c4fff8c 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/hilt/LogoutUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/hilt/LogoutUseCaseTest.kt @@ -6,6 +6,7 @@ import org.fnives.test.showcase.core.login.LogoutUseCase import org.fnives.test.showcase.core.storage.UserDataLocalStorage import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.kotlin.mock import org.mockito.kotlin.times @@ -36,13 +37,15 @@ internal class LogoutUseCaseTest { testCoreComponent.inject(this) } + @DisplayName("WHEN no call THEN storage is not interacted") @Test - fun WHEN_no_call_THEN_storage_is_not_interacted() { + fun initializedDoesntAffectStorage() { verifyZeroInteractions(mockUserDataLocalStorage) } + @DisplayName("WHEN logout invoked THEN storage is cleared") @Test - fun WHEN_logout_invoked_THEN_storage_is_cleared() = runBlockingTest { + fun logoutResultsInStorageCleaning() = runBlockingTest { val repositoryBefore = contentRepository sut.invoke() diff --git a/core/src/test/java/org/fnives/test/showcase/core/login/koin/LogoutUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/login/koin/LogoutUseCaseTest.kt index 78574fe..62a8aba 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/login/koin/LogoutUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/login/koin/LogoutUseCaseTest.kt @@ -9,6 +9,7 @@ import org.fnives.test.showcase.model.network.BaseUrl import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.koin.core.context.startKoin import org.koin.core.context.stopKoin @@ -47,13 +48,15 @@ internal class LogoutUseCaseTest : KoinTest { stopKoin() } + @DisplayName("WHEN no call THEN storage is not interacted") @Test - fun WHEN_no_call_THEN_storage_is_not_interacted() { + fun initializedDoesntAffectStorage() { verifyZeroInteractions(mockUserDataLocalStorage) } + @DisplayName("WHEN logout invoked THEN storage is cleared") @Test - fun WHEN_logout_invoked_THEN_storage_is_cleared() = runBlockingTest { + fun logoutResultsInStorageCleaning() = runBlockingTest { val repositoryBefore = getKoin().get() sut.invoke() diff --git a/core/src/test/java/org/fnives/test/showcase/core/shared/AnswerUtilsKtTest.kt b/core/src/test/java/org/fnives/test/showcase/core/shared/AnswerUtilsKtTest.kt index 7706bb4..f56e137 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/shared/AnswerUtilsKtTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/shared/AnswerUtilsKtTest.kt @@ -7,13 +7,15 @@ import org.fnives.test.showcase.model.shared.Resource import org.fnives.test.showcase.network.shared.exceptions.NetworkException import org.fnives.test.showcase.network.shared.exceptions.ParsingException import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test @Suppress("TestFunctionName") internal class AnswerUtilsKtTest { + @DisplayName("GIVEN network exception thrown WHEN wrapped into answer THEN answer error is returned") @Test - fun GIVEN_network_exception_thrown_WHEN_wrapped_into_answer_THEN_answer_error_is_returned() = runBlocking { + fun networkExceptionThrownResultsInError() = runBlocking { val exception = NetworkException(Throwable()) val expected = Answer.Error(exception) @@ -22,8 +24,9 @@ internal class AnswerUtilsKtTest { Assertions.assertEquals(expected, actual) } + @DisplayName("GIVEN parsing exception thrown WHEN wrapped into answer THEN answer error is returned") @Test - fun GIVEN_parsing_exception_thrown_WHEN_wrapped_into_answer_THEN_answer_error_is_returned() = runBlocking { + fun parsingExceptionThrownResultsInError() = runBlocking { val exception = ParsingException(Throwable()) val expected = Answer.Error(exception) @@ -32,8 +35,9 @@ internal class AnswerUtilsKtTest { Assertions.assertEquals(expected, actual) } + @DisplayName("GIVEN unexpected throwable thrown WHEN wrapped into answer THEN answer error is returned") @Test - fun GIVEN_parsing_throwable_thrown_WHEN_wrapped_into_answer_THEN_answer_error_is_returned() = runBlocking { + fun unexpectedExceptionThrownResultsInError() = runBlocking { val exception = Throwable() val expected = Answer.Error(UnexpectedException(exception)) @@ -42,8 +46,9 @@ internal class AnswerUtilsKtTest { Assertions.assertEquals(expected, actual) } + @DisplayName("GIVEN string WHEN wrapped into answer THEN string answer is returned") @Test - fun GIVEN_string_WHEN_wrapped_into_answer_THEN_string_answer_is_returned() = runBlocking { + fun stringIsReturnedWrappedIntoSuccess() = runBlocking { val expected = Answer.Success("banan") val actual = wrapIntoAnswer { "banan" } @@ -51,15 +56,17 @@ internal class AnswerUtilsKtTest { Assertions.assertEquals(expected, actual) } + @DisplayName("GIVEN cancellation exception WHEN wrapped into answer THEN cancellation exception is thrown") @Test - fun GIVEN_cancellation_exception_WHEN_wrapped_into_answer_THEN_cancellation_exception_is_thrown() { + fun cancellationExceptionResultsInThrowingIt() { Assertions.assertThrows(CancellationException::class.java) { runBlocking { wrapIntoAnswer { throw CancellationException() } } } } + @DisplayName("GIVEN success answer WHEN converted into resource THEN Resource success is returned") @Test - fun GIVEN_success_answer_WHEN_converted_into_resource_THEN_Resource_success_is_returned() { + fun successAnswerConvertsToSuccessResource() { val expected = Resource.Success("alma") val actual = Answer.Success("alma").mapIntoResource() @@ -67,8 +74,9 @@ internal class AnswerUtilsKtTest { Assertions.assertEquals(expected, actual) } + @DisplayName("GIVEN error answer WHEN converted into resource THEN Resource error is returned") @Test - fun GIVEN_error_answer_WHEN_converted_into_resource_THEN_Resource_error_is_returned() { + fun errorAnswerConvertsToErrorResource() { val exception = Throwable() val expected = Resource.Error(exception) diff --git a/core/src/test/java/org/fnives/test/showcase/core/storage/NetworkSessionLocalStorageAdapterTest.kt b/core/src/test/java/org/fnives/test/showcase/core/storage/NetworkSessionLocalStorageAdapterTest.kt index 6202975..df53553 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/storage/NetworkSessionLocalStorageAdapterTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/storage/NetworkSessionLocalStorageAdapterTest.kt @@ -3,6 +3,7 @@ package org.fnives.test.showcase.core.storage import org.fnives.test.showcase.model.session.Session import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock @@ -23,16 +24,18 @@ internal class NetworkSessionLocalStorageAdapterTest { sut = NetworkSessionLocalStorageAdapter(mockUserDataLocalStorage) } + @DisplayName("GIVEN null as session WHEN saved THEN its delegated") @Test - fun GIVEN_null_as_session_WHEN_saved_THEN_its_delegated() { + fun settingNullSessionIsDelegated() { sut.session = null verify(mockUserDataLocalStorage, times(1)).session = null verifyNoMoreInteractions(mockUserDataLocalStorage) } + @DisplayName("GIVEN session WHEN saved THEN its delegated") @Test - fun GIVEN_session_WHEN_saved_THEN_its_delegated() { + fun settingDataAsSessionIsDelegated() { val expected = Session("a", "b") sut.session = Session("a", "b") @@ -41,8 +44,9 @@ internal class NetworkSessionLocalStorageAdapterTest { verifyNoMoreInteractions(mockUserDataLocalStorage) } + @DisplayName("WHEN session requested THEN its returned from delegated") @Test - fun WHEN_session_requested_THEN_its_returned_from_delegated() { + fun gettingSessionReturnsFromDelegate() { val expected = Session("a", "b") whenever(mockUserDataLocalStorage.session).doReturn(expected) From ab3e6a64f0dad06d74be255a595b1014472b0ef0 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Tue, 4 Jan 2022 14:34:46 +0200 Subject: [PATCH 3/7] issue#6 Add DisplayName annotation to ViewModel tests in app module --- app/build.gradle | 2 + .../test/showcase/ui/home/MainViewModel.kt | 3 +- .../testutils/InstantExecutorExtension.kt | 8 ++++ .../showcase/testutils/TestMainDispatcher.kt | 6 +++ .../showcase/ui/auth/AuthViewModelTest.kt | 24 ++++++----- .../showcase/ui/auth/CodeKataAuthViewModel.kt | 8 ++-- .../showcase/ui/home/MainViewModelTest.kt | 43 ++++++++++++------- .../test/showcase/ui/shared/EventTest.kt | 13 ++++-- .../AddContentToFavouriteUseCaseTest.kt | 6 +-- .../auth/CodeKataLoginRemoteSourceTest.kt | 2 +- .../LoginRemoteSourceRefreshActionImplTest.kt | 10 ++--- .../auth/hilt/LoginRemoteSourceTest.kt | 2 +- .../LoginRemoteSourceRefreshActionImplTest.kt | 10 ++--- .../auth/koin/LoginRemoteSourceTest.kt | 2 +- .../content/CodeKataSessionExpirationTest.kt | 2 +- .../content/hilt/SessionExpirationTest.kt | 2 +- .../content/koin/SessionExpirationTest.kt | 2 +- 17 files changed, 92 insertions(+), 53 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 7376036..e704964 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -159,4 +159,6 @@ dependencies { androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version" kaptAndroidTest "com.google.dagger:hilt-compiler:$hilt_version" androidTestImplementation project(":network") // hilt needs it + + implementation "io.reactivex.rxjava3:rxjava:3.1.2" } \ No newline at end of file diff --git a/app/src/main/java/org/fnives/test/showcase/ui/home/MainViewModel.kt b/app/src/main/java/org/fnives/test/showcase/ui/home/MainViewModel.kt index 502d543..14e49fe 100644 --- a/app/src/main/java/org/fnives/test/showcase/ui/home/MainViewModel.kt +++ b/app/src/main/java/org/fnives/test/showcase/ui/home/MainViewModel.kt @@ -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> = _content private val _errorMessage = MutableLiveData(false) - val errorMessage: LiveData = _errorMessage + val errorMessage: LiveData = _errorMessage.distinctUntilChanged() private val _navigateToAuth = MutableLiveData>() val navigateToAuth: LiveData> = _navigateToAuth diff --git a/app/src/test/java/org/fnives/test/showcase/testutils/InstantExecutorExtension.kt b/app/src/test/java/org/fnives/test/showcase/testutils/InstantExecutorExtension.kt index bfd0594..ca9d988 100644 --- a/app/src/test/java/org/fnives/test/showcase/testutils/InstantExecutorExtension.kt +++ b/app/src/test/java/org/fnives/test/showcase/testutils/InstantExecutorExtension.kt @@ -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?) { diff --git a/app/src/test/java/org/fnives/test/showcase/testutils/TestMainDispatcher.kt b/app/src/test/java/org/fnives/test/showcase/testutils/TestMainDispatcher.kt index c00ef75..e4f725c 100644 --- a/app/src/test/java/org/fnives/test/showcase/testutils/TestMainDispatcher.kt +++ b/app/src/test/java/org/fnives/test/showcase/testutils/TestMainDispatcher.kt @@ -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?) { diff --git a/app/src/test/java/org/fnives/test/showcase/ui/auth/AuthViewModelTest.kt b/app/src/test/java/org/fnives/test/showcase/ui/auth/AuthViewModelTest.kt index 263a181..aa690d6 100644 --- a/app/src/test/java/org/fnives/test/showcase/ui/auth/AuthViewModelTest.kt +++ b/app/src/test/java/org/fnives/test/showcase/ui/auth/AuthViewModelTest.kt @@ -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)) } diff --git a/app/src/test/java/org/fnives/test/showcase/ui/auth/CodeKataAuthViewModel.kt b/app/src/test/java/org/fnives/test/showcase/ui/auth/CodeKataAuthViewModel.kt index 1d01ef7..eb996f2 100644 --- a/app/src/test/java/org/fnives/test/showcase/ui/auth/CodeKataAuthViewModel.kt +++ b/app/src/test/java/org/fnives/test/showcase/ui/auth/CodeKataAuthViewModel.kt @@ -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() { } diff --git a/app/src/test/java/org/fnives/test/showcase/ui/home/MainViewModelTest.kt b/app/src/test/java/org/fnives/test/showcase/ui/home/MainViewModelTest.kt index e6f7eb7..bb87c92 100644 --- a/app/src/test/java/org/fnives/test/showcase/ui/home/MainViewModelTest.kt +++ b/app/src/test/java/org/fnives/test/showcase/ui/home/MainViewModelTest.kt @@ -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) diff --git a/app/src/test/java/org/fnives/test/showcase/ui/shared/EventTest.kt b/app/src/test/java/org/fnives/test/showcase/ui/shared/EventTest.kt index 1c1043b..bbcabde 100644 --- a/app/src/test/java/org/fnives/test/showcase/ui/shared/EventTest.kt +++ b/app/src/test/java/org/fnives/test/showcase/ui/shared/EventTest.kt @@ -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() diff --git a/core/src/test/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCaseTest.kt b/core/src/test/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCaseTest.kt index e930064..129d812 100644 --- a/core/src/test/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCaseTest.kt +++ b/core/src/test/java/org/fnives/test/showcase/core/content/AddContentToFavouriteUseCaseTest.kt @@ -28,13 +28,13 @@ internal class AddContentToFavouriteUseCaseTest { sut = AddContentToFavouriteUseCase(mockFavouriteContentLocalStorage) } - @DisplayName("WHEN_nothing_happens_THEN_the_storage_is_not_touched") + @DisplayName("WHEN nothing happens THEN the storage is not touched") @Test fun initializationDoesntAffectStorage() { verifyZeroInteractions(mockFavouriteContentLocalStorage) } - @DisplayName("GIVEN_contentId_WHEN_called_THEN_storage_is_called") + @DisplayName("GIVEN contentId WHEN called THEN storage is called") @Test fun contentIdIsDelegatedToStorage() = runBlockingTest { sut.invoke(ContentId("a")) @@ -43,7 +43,7 @@ internal class AddContentToFavouriteUseCaseTest { verifyNoMoreInteractions(mockFavouriteContentLocalStorage) } - @DisplayName("GIVEN_throwing_local_storage_WHEN_thrown_THEN_its_propagated") + @DisplayName("GIVEN throwing local storage WHEN thrown THEN its propagated") @Test fun storageThrowingIsPropagated() = runBlockingTest { whenever(mockFavouriteContentLocalStorage.markAsFavourite(ContentId("a"))).doThrow( diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/CodeKataLoginRemoteSourceTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/CodeKataLoginRemoteSourceTest.kt index 843e61b..d4ae897 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/CodeKataLoginRemoteSourceTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/CodeKataLoginRemoteSourceTest.kt @@ -35,7 +35,7 @@ class CodeKataLoginRemoteSourceTest { fun badRequestMeansInvalidCredentials() = runBlocking { } - @DisplayName("GIVEN_internal_error_response_WHEN_request_is_fired_THEN_network_exception_is_thrown") + @DisplayName("GIVEN internal error response WHEN request is fired THEN network exception is thrown") @Test fun genericErrorMeansNetworkError() { } diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt index 5490cb0..87705a0 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceRefreshActionImplTest.kt @@ -42,7 +42,7 @@ class LoginRemoteSourceRefreshActionImplTest { .inject(this) } - @DisplayName("GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_session_is_returned") + @DisplayName("GIVEN successful response WHEN refresh request is fired THEN session is returned") @Test fun successResponseResultsInSession() = runBlocking { mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success) @@ -53,7 +53,7 @@ class LoginRemoteSourceRefreshActionImplTest { Assertions.assertEquals(expected, actual) } - @DisplayName("GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_the_request_is_setup_properly") + @DisplayName("GIVEN successful response WHEN refresh request is fired THEN the request is setup properly") @Test fun refreshRequestIsSetupProperly() = runBlocking { mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false) @@ -71,7 +71,7 @@ class LoginRemoteSourceRefreshActionImplTest { Assertions.assertEquals("", request.body.readUtf8()) } - @DisplayName("GIVEN_internal_error_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown") + @DisplayName("GIVEN internal error response WHEN refresh request is fired THEN network exception is thrown") @Test fun generalErrorResponseResultsInNetworkException() { mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error) @@ -81,7 +81,7 @@ class LoginRemoteSourceRefreshActionImplTest { } } - @DisplayName("GIVEN_invalid_json_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown") + @DisplayName("GIVEN invalid json response WHEN refresh request is fired THEN network exception is thrown") @Test fun jsonErrorResponseResultsInParsingException() { mockServerScenarioSetup.setScenario(RefreshTokenScenario.UnexpectedJsonAsSuccessResponse) @@ -91,7 +91,7 @@ class LoginRemoteSourceRefreshActionImplTest { } } - @DisplayName("GIVEN_malformed_json_response_WHEN_refresh_request_is_fired_THEN_parsing_exception_is_thrown") + @DisplayName("GIVEN malformed json response WHEN refresh request is fired THEN parsing exception is thrown") @Test fun malformedJsonErrorResponseResultsInParsingException() { mockServerScenarioSetup.setScenario(RefreshTokenScenario.MalformedJson) diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceTest.kt index 94b848e..3e49a32 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/hilt/LoginRemoteSourceTest.kt @@ -88,7 +88,7 @@ class LoginRemoteSourceTest { Assertions.assertEquals(expected, actual) } - @DisplayName("GIVEN_internal_error_response_WHEN_request_is_fired_THEN_network_exception_is_thrown") + @DisplayName("GIVEN internal error response WHEN request is fired THEN network exception is thrown") @Test fun genericErrorMeansNetworkError() { mockServerScenarioSetup.setScenario(AuthScenario.GenericError("a", "b")) diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt index 80a2a77..1709388 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceRefreshActionImplTest.kt @@ -54,7 +54,7 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest { stopKoin() } - @DisplayName("GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_session_is_returned") + @DisplayName("GIVEN successful response WHEN refresh request is fired THEN session is returned") @Test fun successResponseResultsInSession() = runBlocking { mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success) @@ -65,7 +65,7 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest { Assertions.assertEquals(expected, actual) } - @DisplayName("GIVEN_successful_response_WHEN_refresh_request_is_fired_THEN_the_request_is_setup_properly") + @DisplayName("GIVEN successful response WHEN refresh request is fired THEN the request is setup properly") @Test fun refreshRequestIsSetupProperly() = runBlocking { mockServerScenarioSetup.setScenario(RefreshTokenScenario.Success, false) @@ -80,7 +80,7 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest { Assertions.assertEquals("", request.body.readUtf8()) } - @DisplayName("GIVEN_internal_error_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown") + @DisplayName("GIVEN internal error response WHEN refresh request is fired THEN network exception is thrown") @Test fun generalErrorResponseResultsInNetworkException() { mockServerScenarioSetup.setScenario(RefreshTokenScenario.Error) @@ -90,7 +90,7 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest { } } - @DisplayName("GIVEN_invalid_json_response_WHEN_refresh_request_is_fired_THEN_network_exception_is_thrown") + @DisplayName("GIVEN invalid json response WHEN refresh request is fired THEN network exception is thrown") @Test fun jsonErrorResponseResultsInParsingException() { mockServerScenarioSetup.setScenario(RefreshTokenScenario.UnexpectedJsonAsSuccessResponse) @@ -100,7 +100,7 @@ class LoginRemoteSourceRefreshActionImplTest : KoinTest { } } - @DisplayName("GIVEN_malformed_json_response_WHEN_refresh_request_is_fired_THEN_parsing_exception_is_thrown") + @DisplayName("GIVEN malformed json response WHEN refresh request is fired THEN parsing exception is thrown") @Test fun malformedJsonErrorResponseResultsInParsingException() { mockServerScenarioSetup.setScenario(RefreshTokenScenario.MalformedJson) diff --git a/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceTest.kt b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceTest.kt index cc1b582..56ce6d3 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/auth/koin/LoginRemoteSourceTest.kt @@ -100,7 +100,7 @@ class LoginRemoteSourceTest : KoinTest { Assertions.assertEquals(expected, actual) } - @DisplayName("GIVEN_internal_error_response_WHEN_request_is_fired_THEN_network_exception_is_thrown") + @DisplayName("GIVEN internal error response WHEN request is fired THEN network exception is thrown") @Test fun genericErrorMeansNetworkError() { mockServerScenarioSetup.setScenario(AuthScenario.GenericError("a", "b")) diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt index 625b995..0a68a41 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/CodeKataSessionExpirationTest.kt @@ -49,7 +49,7 @@ class CodeKataSessionExpirationTest : KoinTest { mockWebServer.shutdown() } - @DisplayName("GIVEN_401_THEN_refresh_token_ok_response_WHEN_content_requested_THE_tokens_are_refreshed_and_request_retried_with_new_tokens") + @DisplayName("GIVEN 401 THEN refresh token ok response WHEN content requested THE tokens are refreshed and request retried with new tokens") @Test fun successRefreshResultsInRequestRetry() = runBlocking { } diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt index 9476715..f684fee 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/hilt/SessionExpirationTest.kt @@ -55,7 +55,7 @@ class SessionExpirationTest { .inject(this) } - @DisplayName("GIVEN_401_THEN_refresh_token_ok_response_WHEN_content_requested_THE_tokens_are_refreshed_and_request_retried_with_new_tokens") + @DisplayName("GIVEN 401 THEN refresh token ok response WHEN content requested THE tokens are refreshed and request retried with new tokens") @Test fun successRefreshResultsInRequestRetry() = runBlocking { var sessionToReturnByMock: Session? = ContentData.loginSuccessResponse diff --git a/network/src/test/java/org/fnives/test/showcase/network/content/koin/SessionExpirationTest.kt b/network/src/test/java/org/fnives/test/showcase/network/content/koin/SessionExpirationTest.kt index 5902153..a3fc945 100644 --- a/network/src/test/java/org/fnives/test/showcase/network/content/koin/SessionExpirationTest.kt +++ b/network/src/test/java/org/fnives/test/showcase/network/content/koin/SessionExpirationTest.kt @@ -66,7 +66,7 @@ class SessionExpirationTest : KoinTest { stopKoin() } - @DisplayName("GIVEN_401_THEN_refresh_token_ok_response_WHEN_content_requested_THE_tokens_are_refreshed_and_request_retried_with_new_tokens") + @DisplayName("GIVEN 401 THEN refresh token ok response WHEN content requested THE tokens are refreshed and request retried with new tokens") @Test fun successRefreshResultsInRequestRetry() = runBlocking { var sessionToReturnByMock: Session? = ContentData.loginSuccessResponse From 01a23ee0bdf32bc161e4195266f1728f068f59e4 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Tue, 4 Jan 2022 15:11:59 +0200 Subject: [PATCH 4/7] issue#6 Add GIVEN-WHEN-THEN as documentation and simplify testNames in sharedTests of app module --- .../test/showcase/ui/home/MainActivityTest.kt | 30 +++++++++++------ .../showcase/ui/login/AuthActivityTest.kt | 15 ++++++--- .../showcase/ui/splash/SplashActivityTest.kt | 6 ++-- .../test/showcase/ui/home/MainActivityTest.kt | 33 ++++++++++++------- .../showcase/ui/login/AuthActivityTest.kt | 15 ++++++--- .../showcase/ui/splash/SplashActivityTest.kt | 6 ++-- 6 files changed, 70 insertions(+), 35 deletions(-) diff --git a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt index 6266263..a63bbb7 100644 --- a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt +++ b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt @@ -82,8 +82,9 @@ class MainActivityTest { disposable.dispose() } + /** GIVEN initialized MainActivity WHEN signout is clicked THEN user is signed out */ @Test - fun GIVEN_initialized_MainActivity_WHEN_signout_is_clicked_THEN_user_is_signed_out() { + fun signOutClickedResultsInNavigation() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Error(false)) activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) @@ -96,8 +97,9 @@ class MainActivityTest { Assert.assertEquals(false, setupLoggedInState.isLoggedIn()) } + /** GIVEN success response WHEN data is returned THEN it is shown on the ui */ @Test - fun GIVEN_success_response_WHEN_data_is_returned_THEN_it_is_shown_on_the_ui() { + fun successfulDataLoadingShowsTheElementsOnTheUI() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Success(false)) activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) @@ -109,8 +111,9 @@ class MainActivityTest { homeRobot.assertDidNotNavigateToAuth() } + /** GIVEN success response WHEN item is clicked THEN ui is updated */ @Test - fun GIVEN_success_response_WHEN_item_is_clicked_THEN_ui_is_updated() { + fun clickingOnListElementUpdatesTheElementsFavouriteState() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Success(false)) activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) @@ -124,8 +127,9 @@ class MainActivityTest { .assertDidNotNavigateToAuth() } + /** GIVEN success response WHEN item is clicked THEN ui is updated even if activity is recreated */ @Test - fun GIVEN_success_response_WHEN_item_is_clicked_THEN_ui_is_updated_even_if_activity_is_recreated() { + fun elementFavouritedIsKeptEvenIfActivityIsRecreated() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Success(false)) activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) @@ -144,8 +148,9 @@ class MainActivityTest { .assertDidNotNavigateToAuth() } + /** GIVEN success response WHEN item is clicked then clicked again THEN ui is updated */ @Test - fun GIVEN_success_response_WHEN_item_is_clicked_then_clicked_again_THEN_ui_is_updated() { + fun clickingAnElementMultipleTimesProperlyUpdatesIt() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Success(false)) activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) @@ -161,8 +166,9 @@ class MainActivityTest { .assertDidNotNavigateToAuth() } + /** GIVEN error response WHEN loaded THEN error is Shown */ @Test - fun GIVEN_error_response_WHEN_loaded_THEN_error_is_Shown() { + fun networkErrorResultsInUIErrorStateShown() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Error(false)) activityScenario = ActivityScenario.launch(HiltMainActivity::class.java) @@ -173,8 +179,9 @@ class MainActivityTest { .assertDidNotNavigateToAuth() } + /** GIVEN error response then success WHEN retried THEN success is shown */ @Test - fun GIVEN_error_response_then_success_WHEN_retried_THEN_success_is_shown() { + fun retryingFromErrorStateAndSucceedingShowsTheData() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario( ContentScenario.Error(false) @@ -193,8 +200,9 @@ class MainActivityTest { homeRobot.assertDidNotNavigateToAuth() } + /** GIVEN success then error WHEN retried THEN error is shown */ @Test - fun GIVEN_success_then_error_WHEN_retried_THEN_error_is_shown() { + fun errorIsShownIfTheDataIsFetchedAndErrorIsReceived() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario( ContentScenario.Success(false) @@ -215,8 +223,9 @@ class MainActivityTest { .assertDidNotNavigateToAuth() } + /** GIVEN unauthenticated then success WHEN loaded THEN success is shown */ @Test - fun GIVEN_unauthenticated_then_success_WHEN_loaded_THEN_success_is_shown() { + fun authenticationIsHandledWithASingleLoading() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario( ContentScenario.Unauthorized(false) @@ -233,8 +242,9 @@ class MainActivityTest { homeRobot.assertDidNotNavigateToAuth() } + /** GIVEN unauthenticated then error WHEN loaded THEN navigated to auth */ @Test - fun GIVEN_unauthenticated_then_error_WHEN_loaded_THEN_navigated_to_auth() { + fun sessionExpirationResultsInNavigation() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Unauthorized(false)) .setScenario(RefreshTokenScenario.Error) diff --git a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt index 3183967..b1ec4e2 100644 --- a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt +++ b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt @@ -72,8 +72,9 @@ class AuthActivityTest { disposable.dispose() } + /** GIVEN non empty password and username and successful response WHEN signIn THEN no error is shown and navigating to home */ @Test - fun GIVEN_non_empty_password_and_username_and_successful_response_WHEN_signIn_THEN_no_error_is_shown_and_navigating_to_home() { + fun properLoginResultsInNavigationToHome() { mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario( AuthScenario.Success( password = "alma", @@ -93,8 +94,9 @@ class AuthActivityTest { loginRobot.assertNavigatedToHome() } + /** GIVEN empty password and username WHEN signIn THEN error password is shown */ @Test - fun GIVEN_empty_password_and_username_WHEN_signIn_THEN_error_password_is_shown() { + fun emptyPasswordShowsProperErrorMessage() { activityScenario = ActivityScenario.launch(HiltAuthActivity::class.java) loginRobot .setUsername("banan") @@ -108,8 +110,9 @@ class AuthActivityTest { .assertNotLoading() } + /** GIVEN password and empty username WHEN signIn THEN error username is shown */ @Test - fun GIVEN_password_and_empty_username_WHEN_signIn_THEN_error_username_is_shown() { + fun emptyUserNameShowsProperErrorMessage() { activityScenario = ActivityScenario.launch(HiltAuthActivity::class.java) loginRobot .setPassword("banan") @@ -123,8 +126,9 @@ class AuthActivityTest { .assertNotLoading() } + /** GIVEN password and username and invalid credentials response WHEN signIn THEN error invalid credentials is shown */ @Test - fun GIVEN_password_and_username_and_invalid_credentials_response_WHEN_signIn_THEN_error_invalid_credentials_is_shown() { + fun invalidCredentialsGivenShowsProperErrorMessage() { mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario( AuthScenario.InvalidCredentials(username = "alma", password = "banan") ) @@ -143,8 +147,9 @@ class AuthActivityTest { .assertNotLoading() } + /** GIVEN password and username and error response WHEN signIn THEN error invalid credentials is shown */ @Test - fun GIVEN_password_and_username_and_error_response_WHEN_signIn_THEN_error_invalid_credentials_is_shown() { + fun networkErrorShowsProperErrorMessage() { mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario( AuthScenario.GenericError(username = "alma", password = "banan") ) diff --git a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt index 788e0a2..1caf222 100644 --- a/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt +++ b/app/src/sharedTestHilt/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt @@ -71,8 +71,9 @@ class SplashActivityTest : KoinTest { disposable?.dispose() } + /** GIVEN loggedInState WHEN opened THEN MainActivity is started */ @Test - fun GIVEN_loggedInState_WHEN_opened_THEN_MainActivity_is_started() { + fun loggedInStateNavigatesToHome() { setupLoggedInState.setupLogin(mockServerScenarioSetupTestRule.mockServerScenarioSetup) activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java) @@ -85,8 +86,9 @@ class SplashActivityTest : KoinTest { setupLoggedInState.setupLogout() } + /** GIVEN loggedOffState WHEN opened THEN AuthActivity is started */ @Test - fun GIVEN_loggedOffState_WHEN_opened_THEN_AuthActivity_is_started() { + fun loggedOutStatesNavigatesToAuthentication() { setupLoggedInState.setupLogout() activityScenario = ActivityScenario.launch(HiltSplashActivity::class.java) diff --git a/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt index b2c9572..a26e2f6 100644 --- a/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt +++ b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/home/MainActivityTest.kt @@ -37,7 +37,8 @@ class MainActivityTest : KoinTest { @Rule @JvmField - val snackbarVerificationTestRule = SpecificTestConfigurationsFactory.createSnackbarVerification() + val snackbarVerificationTestRule = + SpecificTestConfigurationsFactory.createSnackbarVerification() @Rule @JvmField @@ -73,8 +74,9 @@ class MainActivityTest : KoinTest { disposable.dispose() } + /** GIVEN initialized MainActivity WHEN signout is clicked THEN user is signed out */ @Test - fun GIVEN_initialized_MainActivity_WHEN_signout_is_clicked_THEN_user_is_signed_out() { + fun signOutClickedResultsInNavigation() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Error(false)) activityScenario = ActivityScenario.launch(MainActivity::class.java) @@ -87,8 +89,9 @@ class MainActivityTest : KoinTest { Assert.assertEquals(false, SetupLoggedInState.isLoggedIn()) } + /** GIVEN success response WHEN data is returned THEN it is shown on the ui */ @Test - fun GIVEN_success_response_WHEN_data_is_returned_THEN_it_is_shown_on_the_ui() { + fun successfulDataLoadingShowsTheElementsOnTheUI() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Success(false)) activityScenario = ActivityScenario.launch(MainActivity::class.java) @@ -100,8 +103,9 @@ class MainActivityTest : KoinTest { homeRobot.assertDidNotNavigateToAuth() } + /** GIVEN success response WHEN item is clicked THEN ui is updated */ @Test - fun GIVEN_success_response_WHEN_item_is_clicked_THEN_ui_is_updated() { + fun clickingOnListElementUpdatesTheElementsFavouriteState() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Success(false)) activityScenario = ActivityScenario.launch(MainActivity::class.java) @@ -115,8 +119,9 @@ class MainActivityTest : KoinTest { .assertDidNotNavigateToAuth() } + /** GIVEN success response WHEN item is clicked THEN ui is updated even if activity is recreated */ @Test - fun GIVEN_success_response_WHEN_item_is_clicked_THEN_ui_is_updated_even_if_activity_is_recreated() { + fun elementFavouritedIsKeptEvenIfActivityIsRecreated() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Success(false)) activityScenario = ActivityScenario.launch(MainActivity::class.java) @@ -135,8 +140,9 @@ class MainActivityTest : KoinTest { .assertDidNotNavigateToAuth() } + /** GIVEN success response WHEN item is clicked then clicked again THEN ui is updated */ @Test - fun GIVEN_success_response_WHEN_item_is_clicked_then_clicked_again_THEN_ui_is_updated() { + fun clickingAnElementMultipleTimesProperlyUpdatesIt() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Success(false)) activityScenario = ActivityScenario.launch(MainActivity::class.java) @@ -152,8 +158,9 @@ class MainActivityTest : KoinTest { .assertDidNotNavigateToAuth() } + /** GIVEN error response WHEN loaded THEN error is Shown */ @Test - fun GIVEN_error_response_WHEN_loaded_THEN_error_is_Shown() { + fun networkErrorResultsInUIErrorStateShown() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Error(false)) activityScenario = ActivityScenario.launch(MainActivity::class.java) @@ -164,8 +171,9 @@ class MainActivityTest : KoinTest { .assertDidNotNavigateToAuth() } + /** GIVEN error response then success WHEN retried THEN success is shown */ @Test - fun GIVEN_error_response_then_success_WHEN_retried_THEN_success_is_shown() { + fun retryingFromErrorStateAndSucceedingShowsTheData() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario( ContentScenario.Error(false) @@ -184,8 +192,9 @@ class MainActivityTest : KoinTest { homeRobot.assertDidNotNavigateToAuth() } + /** GIVEN success then error WHEN retried THEN error is shown */ @Test - fun GIVEN_success_then_error_WHEN_retried_THEN_error_is_shown() { + fun errorIsShownIfTheDataIsFetchedAndErrorIsReceived() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario( ContentScenario.Success(false) @@ -206,8 +215,9 @@ class MainActivityTest : KoinTest { .assertDidNotNavigateToAuth() } + /** GIVEN unauthenticated then success WHEN loaded THEN success is shown */ @Test - fun GIVEN_unauthenticated_then_success_WHEN_loaded_THEN_success_is_shown() { + fun authenticationIsHandledWithASingleLoading() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario( ContentScenario.Unauthorized(false) @@ -224,8 +234,9 @@ class MainActivityTest : KoinTest { homeRobot.assertDidNotNavigateToAuth() } + /** GIVEN unauthenticated then error WHEN loaded THEN navigated to auth */ @Test - fun GIVEN_unauthenticated_then_error_WHEN_loaded_THEN_navigated_to_auth() { + fun sessionExpirationResultsInNavigation() { mockServerScenarioSetupTestRule.mockServerScenarioSetup .setScenario(ContentScenario.Unauthorized(false)) .setScenario(RefreshTokenScenario.Error) diff --git a/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt index e971de2..643be01 100644 --- a/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt +++ b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/login/AuthActivityTest.kt @@ -66,8 +66,9 @@ class AuthActivityTest : KoinTest { disposable.dispose() } + /** GIVEN non empty password and username and successful response WHEN signIn THEN no error is shown and navigating to home */ @Test - fun GIVEN_non_empty_password_and_username_and_successful_response_WHEN_signIn_THEN_no_error_is_shown_and_navigating_to_home() { + fun properLoginResultsInNavigationToHome() { mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario( AuthScenario.Success( password = "alma", @@ -87,8 +88,9 @@ class AuthActivityTest : KoinTest { loginRobot.assertNavigatedToHome() } + /** GIVEN empty password and username WHEN signIn THEN error password is shown */ @Test - fun GIVEN_empty_password_and_username_WHEN_signIn_THEN_error_password_is_shown() { + fun emptyPasswordShowsProperErrorMessage() { activityScenario = ActivityScenario.launch(AuthActivity::class.java) loginRobot .setUsername("banan") @@ -102,8 +104,9 @@ class AuthActivityTest : KoinTest { .assertNotLoading() } + /** GIVEN password and empty username WHEN signIn THEN error username is shown */ @Test - fun GIVEN_password_and_empty_username_WHEN_signIn_THEN_error_username_is_shown() { + fun emptyUserNameShowsProperErrorMessage() { activityScenario = ActivityScenario.launch(AuthActivity::class.java) loginRobot .setPassword("banan") @@ -117,8 +120,9 @@ class AuthActivityTest : KoinTest { .assertNotLoading() } + /** GIVEN password and username and invalid credentials response WHEN signIn THEN error invalid credentials is shown */ @Test - fun GIVEN_password_and_username_and_invalid_credentials_response_WHEN_signIn_THEN_error_invalid_credentials_is_shown() { + fun invalidCredentialsGivenShowsProperErrorMessage() { mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario( AuthScenario.InvalidCredentials(username = "alma", password = "banan") ) @@ -137,8 +141,9 @@ class AuthActivityTest : KoinTest { .assertNotLoading() } + /** GIVEN password and username and error response WHEN signIn THEN error invalid credentials is shown */ @Test - fun GIVEN_password_and_username_and_error_response_WHEN_signIn_THEN_error_invalid_credentials_is_shown() { + fun networkErrorShowsProperErrorMessage() { mockServerScenarioSetupTestRule.mockServerScenarioSetup.setScenario( AuthScenario.GenericError(username = "alma", password = "banan") ) diff --git a/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt index ea8c92b..c7b1a1a 100644 --- a/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt +++ b/app/src/sharedTestKoin/java/org/fnives/test/showcase/ui/splash/SplashActivityTest.kt @@ -61,8 +61,9 @@ class SplashActivityTest : KoinTest { disposable.dispose() } + /** GIVEN loggedInState WHEN opened THEN MainActivity is started */ @Test - fun GIVEN_loggedInState_WHEN_opened_THEN_MainActivity_is_started() { + fun loggedInStateNavigatesToHome() { SetupLoggedInState.setupLogin(mockServerScenarioSetupTestRule.mockServerScenarioSetup) activityScenario = ActivityScenario.launch(SplashActivity::class.java) @@ -75,8 +76,9 @@ class SplashActivityTest : KoinTest { SetupLoggedInState.setupLogout() } + /** GIVEN loggedOffState WHEN opened THEN AuthActivity is started */ @Test - fun GIVEN_loggedOffState_WHEN_opened_THEN_AuthActivity_is_started() { + fun loggedOutStatesNavigatesToAuthentication() { SetupLoggedInState.setupLogout() activityScenario = ActivityScenario.launch(SplashActivity::class.java) From 65d04db3748fcc37f891ac487110cf721604afa4 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Tue, 4 Jan 2022 15:14:54 +0200 Subject: [PATCH 5/7] issue#6 Add GIVEN-WHEN-THEN as documentation and simplify testNames in robolectricTests of app module --- .../FavouriteContentLocalStorageImplTest.kt | 12 ++++++++---- .../FavouriteContentLocalStorageImplTest.kt | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/src/robolectricTestHilt/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt b/app/src/robolectricTestHilt/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt index 7166b68..b58f4a3 100644 --- a/app/src/robolectricTestHilt/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt +++ b/app/src/robolectricTestHilt/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt @@ -40,8 +40,9 @@ internal class FavouriteContentLocalStorageImplTest { hiltRule.inject() } + /** GIVEN content_id WHEN added to Favourite THEN it can be read out */ @Test - fun GIVEN_content_id_WHEN_added_to_Favourite_THEN_it_can_be_read_out() = runBlocking { + fun addingContentIdToFavouriteCanBeLaterReadOut() = runBlocking { val expected = listOf(ContentId("a")) sut.markAsFavourite(ContentId("a")) @@ -50,8 +51,9 @@ internal class FavouriteContentLocalStorageImplTest { Assert.assertEquals(expected, actual) } + /** GIVEN content_id added WHEN removed to Favourite THEN it no longer can be read out */ @Test - fun GIVEN_content_id_added_WHEN_removed_to_Favourite_THEN_it_no_longer_can_be_read_out() = + fun contentIdAddedThenRemovedCanNoLongerBeReadOut() = runBlocking { val expected = listOf() sut.markAsFavourite(ContentId("b")) @@ -62,8 +64,9 @@ internal class FavouriteContentLocalStorageImplTest { Assert.assertEquals(expected, actual) } + /** GIVEN empty database WHILE observing content WHEN favourite added THEN change is emitted */ @Test - fun GIVEN_empty_database_WHILE_observing_content_WHEN_favourite_added_THEN_change_is_emitted() = + fun addingFavouriteUpdatesExistingObservers() = runBlocking { val expected = listOf(listOf(), listOf(ContentId("a"))) @@ -78,8 +81,9 @@ internal class FavouriteContentLocalStorageImplTest { Assert.assertEquals(expected, actual.await()) } + /** GIVEN non empty database WHILE observing content WHEN favourite removed THEN change is emitted */ @Test - fun GIVEN_non_empty_database_WHILE_observing_content_WHEN_favourite_removed_THEN_change_is_emitted() = + fun removingFavouriteUpdatesExistingObservers() = runBlocking { val expected = listOf(listOf(ContentId("a")), listOf()) sut.markAsFavourite(ContentId("a")) diff --git a/app/src/robolectricTestKoin/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt b/app/src/robolectricTestKoin/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt index 4434ed6..dc5073e 100644 --- a/app/src/robolectricTestKoin/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt +++ b/app/src/robolectricTestKoin/java/org/fnives/test/showcase/favourite/FavouriteContentLocalStorageImplTest.kt @@ -39,8 +39,9 @@ internal class FavouriteContentLocalStorageImplTest : KoinTest { stopKoin() } + /** GIVEN content_id WHEN added to Favourite THEN it can be read out */ @Test - fun GIVEN_content_id_WHEN_added_to_Favourite_THEN_it_can_be_read_out() = runBlocking { + fun addingContentIdToFavouriteCanBeLaterReadOut() = runBlocking { val expected = listOf(ContentId("a")) sut.markAsFavourite(ContentId("a")) @@ -49,8 +50,9 @@ internal class FavouriteContentLocalStorageImplTest : KoinTest { Assert.assertEquals(expected, actual) } + /** GIVEN content_id added WHEN removed to Favourite THEN it no longer can be read out */ @Test - fun GIVEN_content_id_added_WHEN_removed_to_Favourite_THEN_it_no_longer_can_be_read_out() = + fun contentIdAddedThenRemovedCanNoLongerBeReadOut() = runBlocking { val expected = listOf() sut.markAsFavourite(ContentId("b")) @@ -61,8 +63,9 @@ internal class FavouriteContentLocalStorageImplTest : KoinTest { Assert.assertEquals(expected, actual) } + /** GIVEN empty database WHILE observing content WHEN favourite added THEN change is emitted */ @Test - fun GIVEN_empty_database_WHILE_observing_content_WHEN_favourite_added_THEN_change_is_emitted() = + fun addingFavouriteUpdatesExistingObservers() = runBlocking { val expected = listOf(listOf(), listOf(ContentId("a"))) @@ -77,8 +80,9 @@ internal class FavouriteContentLocalStorageImplTest : KoinTest { Assert.assertEquals(expected, actual.await()) } + /** GIVEN non empty database WHILE observing content WHEN favourite removed THEN change is emitted */ @Test - fun GIVEN_non_empty_database_WHILE_observing_content_WHEN_favourite_removed_THEN_change_is_emitted() = + fun removingFavouriteUpdatesExistingObservers() = runBlocking { val expected = listOf(listOf(ContentId("a")), listOf()) sut.markAsFavourite(ContentId("a")) From 5fe8def39043b2eaa162a707a245fbc3d0c7a601 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Tue, 4 Jan 2022 15:26:11 +0200 Subject: [PATCH 6/7] PR#32 Updated dependency versions pointed out by lint --- app/build.gradle | 1 + gradlescripts/versions.gradle | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e704964..d240085 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,6 +12,7 @@ android { defaultConfig { applicationId "org.fnives.test.showcase" minSdkVersion 21 + //OldTargetApi targetSdkVersion 31 versionCode 1 versionName "1.0" diff --git a/gradlescripts/versions.gradle b/gradlescripts/versions.gradle index 8505e17..8c76f7c 100644 --- a/gradlescripts/versions.gradle +++ b/gradlescripts/versions.gradle @@ -1,14 +1,14 @@ project.ext { androidx_core_version = "1.7.0" - androidx_appcompat_version = "1.3.1" + androidx_appcompat_version = "1.4.0" androidx_material_version = "1.4.0" - androidx_constraintlayout_version = "2.1.1" + androidx_constraintlayout_version = "2.1.2" androidx_livedata_version = "2.4.0" androidx_swiperefreshlayout_version = "1.1.0" - androidx_room_version = "2.3.0" + androidx_room_version = "2.4.0" activity_ktx_version = "1.4.0" - coroutines_version = "1.4.3" + coroutines_version = "1.5.2" koin_version = "3.1.2" coil_version = "1.1.1" retrofit_version = "2.9.0" From ef14673a32fec322da7669feb042f27f50f9fd81 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Tue, 4 Jan 2022 17:49:07 +0200 Subject: [PATCH 7/7] PR#32 Fix lint issues, remove Jake's IdlingResources, since it won't be updated anymore --- app/build.gradle | 19 +++++--- .../testutils/idling/OkHttp3IdlingResource.kt | 46 +++++++++++++++++++ .../idling/NetworkSynchronization.kt | 1 - .../idling/NetworkSynchronization.kt | 1 - build.gradle | 2 +- gradle.properties | 2 +- gradlescripts/versions.gradle | 1 - 7 files changed, 60 insertions(+), 12 deletions(-) create mode 100644 app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/OkHttp3IdlingResource.kt diff --git a/app/build.gradle b/app/build.gradle index d240085..25c7ae8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,13 +7,12 @@ plugins { } android { - compileSdkVersion 31 + compileSdk 31 defaultConfig { applicationId "org.fnives.test.showcase" - minSdkVersion 21 - //OldTargetApi - targetSdkVersion 31 + minSdk 21 + targetSdk 31 versionCode 1 versionName "1.0" buildConfigField "String", "BASE_URL", '"https://606844a10add49001733fe6b.mockapi.io/"' @@ -139,7 +138,6 @@ dependencies { testImplementation "androidx.test.espresso:espresso-core:$testing_espresso_version" testImplementation "androidx.test.espresso:espresso-intents:$testing_espresso_version" testImplementation project(':mockserver') - testImplementation "com.jakewharton.espresso:okhttp3-idling-resource:$testing_okhttp3_idling_resource_version" testImplementation "androidx.arch.core:core-testing:$testing_androidx_arch_core_version" testRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version" testImplementation "com.google.dagger:hilt-android-testing:$hilt_version" @@ -154,7 +152,6 @@ dependencies { androidTestImplementation "androidx.test.espresso:espresso-core:$testing_espresso_version" androidTestImplementation "androidx.test.espresso:espresso-intents:$testing_espresso_version" androidTestImplementation project(':mockserver') - androidTestImplementation "com.jakewharton.espresso:okhttp3-idling-resource:$testing_okhttp3_idling_resource_version" androidTestImplementation "androidx.arch.core:core-testing:$testing_androidx_arch_core_version" androidTestRuntimeOnly "org.junit.vintage:junit-vintage-engine:$testing_junit5_version" androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_version" @@ -162,4 +159,12 @@ dependencies { androidTestImplementation project(":network") // hilt needs it implementation "io.reactivex.rxjava3:rxjava:3.1.2" -} \ No newline at end of file +} + + +///Users/gergelyhegedus/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.5.31/ff5d99aecd328872494e8921b72bf6e3af97af3e/kotlin-stdlib-jdk8-1.5.31.jar (version 1.5) +///Users/gergelyhegedus/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk7/1.5.31/77e0f2568912e45d26c31fd417a332458508acdf/kotlin-stdlib-jdk7-1.5.31.jar (version 1.5) +///Users/gergelyhegedus/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.6.0/a40b8b22529b733892edf4b73468ce598bb17f04/kotlin-stdlib-1.6.0.jar (version 1.6) +///Users/gergelyhegedus/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.6.0/7857e365f925cfa060f941c1357cda1f8790502c/kotlin-stdlib-common-1.6.0.jar (version 1.6) +//w: Some runtime JAR files in the classpath have an incompatible version. Consider removing them from the classpath +//dagger.lint.DaggerIssueRegistry in /Users/gergelyhegedus/.gradle/caches/transforms-3/7d8d9a87fed97b25e3e147795231ede4/transformed/jetified-dagger-lint-aar-2.40/jars/lint.jar does not specify a vendor; see IssueRegistry#vendor diff --git a/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/OkHttp3IdlingResource.kt b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/OkHttp3IdlingResource.kt new file mode 100644 index 0000000..c22ad2f --- /dev/null +++ b/app/src/sharedTest/java/org/fnives/test/showcase/testutils/idling/OkHttp3IdlingResource.kt @@ -0,0 +1,46 @@ +package org.fnives.test.showcase.testutils.idling + +import androidx.annotation.CheckResult +import androidx.annotation.NonNull +import androidx.test.espresso.IdlingResource +import okhttp3.Dispatcher +import okhttp3.OkHttpClient + +/** + * AndroidX version of Jake Wharton's OkHttp3IdlingResource. + * + * Reference: https://github.com/JakeWharton/okhttp-idling-resource/blob/master/src/main/java/com/jakewharton/espresso/OkHttp3IdlingResource.java + */ +class OkHttp3IdlingResource private constructor( + private val name: String, + private val dispatcher: Dispatcher +) : IdlingResource { + @Volatile + var callback: IdlingResource.ResourceCallback? = null + + init { + dispatcher.idleCallback = Runnable { callback?.onTransitionToIdle() } + } + + override fun getName(): String = name + + override fun isIdleNow(): Boolean = dispatcher.runningCallsCount() == 0 + + override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) { + this.callback = callback + } + + companion object { + /** + * Create a new [IdlingResource] from `client` as `name`. You must register + * this instance using `Espresso.registerIdlingResources`. + */ + @CheckResult + @NonNull // Extra guards as a library. + fun create(@NonNull name: String?, @NonNull client: OkHttpClient?): OkHttp3IdlingResource { + if (name == null) throw NullPointerException("name == null") + if (client == null) throw NullPointerException("client == null") + return OkHttp3IdlingResource(name, client.dispatcher) + } + } +} diff --git a/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt b/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt index 5cef5c9..2a81671 100644 --- a/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt +++ b/app/src/sharedTestHilt/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt @@ -2,7 +2,6 @@ package org.fnives.test.showcase.testutils.idling import androidx.annotation.CheckResult import androidx.test.espresso.IdlingResource -import com.jakewharton.espresso.OkHttp3IdlingResource import okhttp3.OkHttpClient import org.fnives.test.showcase.hilt.SessionLessQualifier import org.fnives.test.showcase.hilt.SessionQualifier diff --git a/app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt b/app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt index a6fca71..7bcb095 100644 --- a/app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt +++ b/app/src/sharedTestKoin/java/org/fnives/test/showcase/testutils/idling/NetworkSynchronization.kt @@ -2,7 +2,6 @@ package org.fnives.test.showcase.testutils.idling import androidx.annotation.CheckResult import androidx.test.espresso.IdlingResource -import com.jakewharton.espresso.OkHttp3IdlingResource import okhttp3.OkHttpClient import org.koin.core.qualifier.StringQualifier import org.koin.test.KoinTest diff --git a/build.gradle b/build.gradle index dad6a57..791926d 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ buildscript { } dependencies { classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" - classpath 'com.android.tools.build:gradle:7.0.3' + classpath 'com.android.tools.build:gradle:7.0.4' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.0" } diff --git a/gradle.properties b/gradle.properties index 349b5c9..3852b81 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,7 +16,7 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX -android.enableJetifier=true +android.enableJetifier=false android.jetifier.ignorelist=bcprov-jdk15on # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official \ No newline at end of file diff --git a/gradlescripts/versions.gradle b/gradlescripts/versions.gradle index 8c76f7c..2a3c544 100644 --- a/gradlescripts/versions.gradle +++ b/gradlescripts/versions.gradle @@ -26,5 +26,4 @@ project.ext { testing_junit4_version = "4.13.2" testing_robolectric_version = "4.6.1" testing_espresso_version = "3.4.0" - testing_okhttp3_idling_resource_version = "1.0.0" } \ No newline at end of file