diff --git a/transport-eta-android/mobile-ui/build.gradle b/transport-eta-android/mobile-ui/build.gradle index ce721e3..5abfea6 100644 --- a/transport-eta-android/mobile-ui/build.gradle +++ b/transport-eta-android/mobile-ui/build.gradle @@ -122,7 +122,8 @@ dependencies { // Android unit tests kaptAndroidTest deps.dagger.compiler androidTestImplementation deps.javax.inject - androidTestImplementation deps.dexmaker.linkedin_inline + androidTestImplementation deps.mockito.android +// androidTestImplementation deps.dexmaker.linkedin_inline // Android Testing Support runner and rules and AAC androidTestImplementation deps.atsl.rules androidTestImplementation deps.atsl.runner diff --git a/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestActivityBindingModule.kt b/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestActivityBindingModule.kt index 3dc48f2..2873afc 100644 --- a/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestActivityBindingModule.kt +++ b/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestActivityBindingModule.kt @@ -9,6 +9,6 @@ import dagger.android.ContributesAndroidInjector abstract class TestActivityBindingModule { @PerActivity // HomeFragmentsBuildersModule::class - @ContributesAndroidInjector(modules = arrayOf(TestFragmentActivityModule::class, TestHomeFragmentsBuildersModule::class)) + @ContributesAndroidInjector(modules = arrayOf(TestFragmentActivityModule::class, TestFragmentsBuildersModule::class)) abstract fun bindTestFragmentActivity(): TestFragmentActivity } \ No newline at end of file diff --git a/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestFavoriteFragmentModule.kt b/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestFavoriteFragmentModule.kt index 46c1a46..cb71515 100644 --- a/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestFavoriteFragmentModule.kt +++ b/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestFavoriteFragmentModule.kt @@ -1,6 +1,7 @@ package com.joaquimley.transporteta.ui.di.module import com.joaquimley.transporteta.presentation.home.favorite.FavoritesViewModelFactory +import com.joaquimley.transporteta.presentation.home.favorite.FavoritesViewModelFactoryImpl import dagger.Module import dagger.Provides import org.mockito.Mockito.mock diff --git a/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestHomeFragmentsBuildersModule.kt b/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestFragmentsBuildersModule.kt similarity index 87% rename from transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestHomeFragmentsBuildersModule.kt rename to transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestFragmentsBuildersModule.kt index 88ec6d9..e7d74f7 100644 --- a/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestHomeFragmentsBuildersModule.kt +++ b/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/di/module/TestFragmentsBuildersModule.kt @@ -5,7 +5,7 @@ import dagger.Module import dagger.android.ContributesAndroidInjector @Module -abstract class TestHomeFragmentsBuildersModule { +abstract class TestFragmentsBuildersModule { @ContributesAndroidInjector(modules = arrayOf(TestFavoriteFragmentModule::class)) abstract fun contributeFavouritesFragment(): FavoritesFragment diff --git a/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/home/FavoritesFragmentTest.kt b/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/home/FavoritesFragmentTest.kt index 72f06ba..fef246c 100644 --- a/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/home/FavoritesFragmentTest.kt +++ b/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/home/FavoritesFragmentTest.kt @@ -6,6 +6,7 @@ import android.support.test.espresso.Espresso.onView import android.support.test.espresso.action.ViewActions.* import android.support.test.espresso.assertion.ViewAssertions.doesNotExist import android.support.test.espresso.assertion.ViewAssertions.matches +import android.support.test.espresso.contrib.RecyclerViewActions import android.support.test.espresso.matcher.ViewMatchers.* import android.support.test.filters.MediumTest import android.support.test.rule.ActivityTestRule @@ -15,10 +16,13 @@ import com.joaquimley.transporteta.presentation.data.Resource import com.joaquimley.transporteta.presentation.home.favorite.FavoritesViewModel import com.joaquimley.transporteta.presentation.model.FavoriteView import com.joaquimley.transporteta.ui.di.module.TestFavoriteFragmentModule +import com.joaquimley.transporteta.ui.home.favorite.FavoritesAdapter import com.joaquimley.transporteta.ui.home.favorite.FavoritesFragment import com.joaquimley.transporteta.ui.test.util.RecyclerViewMatcher import com.joaquimley.transporteta.ui.testing.TestFragmentActivity import com.joaquimley.transporteta.ui.testing.factory.TestFactoryFavoriteView +import org.hamcrest.CoreMatchers.not +import org.hamcrest.CoreMatchers.nullValue import org.junit.Before import org.junit.Rule import org.junit.Test @@ -26,11 +30,12 @@ import org.junit.runner.RunWith import org.mockito.Mockito.`when` import org.mockito.Mockito.mock + @MediumTest @RunWith(AndroidJUnit4::class) class FavoritesFragmentTest { - @Rule @JvmField val activityRule = ActivityTestRule(TestFragmentActivity::class.java, true, true) + @Rule @JvmField val activityRule = ActivityTestRule(TestFragmentActivity::class.java, false, true) @Rule @JvmField val instantTaskExecutorRule = InstantTaskExecutorRule() private val results = MutableLiveData>>() @@ -41,11 +46,13 @@ class FavoritesFragmentTest { @Before fun setup() { // Init mock ViewModel - `when`(TestFavoriteFragmentModule.favoritesViewModelsFactory.create()).thenReturn(viewModel) + `when`(TestFavoriteFragmentModule.favoritesViewModelsFactory.create(FavoritesViewModel::class.java)).thenReturn(viewModel) `when`(viewModel.getFavourites()).thenReturn(results) // Instantiate fragment and add to the TestFragmentActivity favoritesFragment = FavoritesFragment.newInstance() activityRule.activity.addFragment(favoritesFragment) + + // Due to Android P non-sdk access we're getting an alert dialog making the tests flaky } @Test @@ -59,7 +66,8 @@ class FavoritesFragmentTest { val resultsList = TestFactoryFavoriteView.generateFavoriteViewList() results.postValue(Resource.success(resultsList)) // Then - onView(withId(R.id.message_view)).check(doesNotExist()) + onView(withId(R.id.progress_bar)).check(matches(not(isDisplayed()))) + onView(withId(R.id.message_view)).check(matches(not(isDisplayed()))) onView(withId(R.id.recycler_view)).check(matches(isDisplayed())) } @@ -68,7 +76,7 @@ class FavoritesFragmentTest { // When results.postValue(Resource.empty()) // Then - onView(withId(R.id.recycler_view)).check(doesNotExist()) + onView(withId(R.id.recycler_view)).check(matches(not(isDisplayed()))) onView(withId(R.id.message_view)).check(matches(isDisplayed())) } @@ -81,7 +89,7 @@ class FavoritesFragmentTest { results.postValue(Resource.error(errorMessage)) // Then onView(withId(R.id.message_view)).check(matches(isDisplayed())) - onView(withId(R.id.recycler_view)).check(doesNotExist()) + onView(withId(R.id.recycler_view)).check(matches(not(isDisplayed()))) } @Test @@ -90,13 +98,16 @@ class FavoritesFragmentTest { val resultsList = TestFactoryFavoriteView.generateFavoriteViewList() results.postValue(Resource.success(resultsList)) onView(withId(R.id.recycler_view)).check(matches(isDisplayed())) + // Error occurs - val errorMessage = "Test for error message" + val errorMessage = "Error message for Test" results.postValue(Resource.error(errorMessage)) + // Then onView(withId(R.id.recycler_view)).check(matches(isDisplayed())) - onView(withId(R.id.message_view)).check(doesNotExist()) - // Only snackbar is shown with retry button + onView(withId(R.id.message_view)).check(matches(not(isDisplayed()))) + + // Snackbar is shown with retry button onView(withText(errorMessage)).check(matches(isDisplayed())) onView(withText(R.string.action_retry)).check(matches(isDisplayed())) } @@ -105,14 +116,15 @@ class FavoritesFragmentTest { fun whenCreateFabIsClickedCreateFavoriteScreenIsShown() { // When onView(withId(R.id.fab)).perform(click()) - // Check dialog is showing + // Check dialog is showing correctly onView(withText(R.string.create_favorite_title)).check(matches(isDisplayed())) - // Check onView(withId(R.id.favorite_code_edit_text)).check(matches(isDisplayed())) onView(withId(R.id.favorite_title_edit_text)).check(matches(isDisplayed())) - onView(withId(R.id.favorite_code_edit_text)).check(matches(withHint(R.string.create_favorite_code_hint))) - onView(withId(R.id.favorite_title_edit_text)).check(matches(withHint(R.string.create_favorite_title_hint))) + // TODO Fix hints +// onView(withText(R.string.create_favorite_code_hint)).check(matches(isDisplayed())) +// onView(withText(R.string.create_favorite_title_hint)).check(matches(isDisplayed())) + // Dialog Action buttons onView(withText(R.string.action_create)).check(matches(isDisplayed())) onView(withText(R.string.action_discard)).check(matches(isDisplayed())) } @@ -125,11 +137,13 @@ class FavoritesFragmentTest { onView(withText(R.string.action_discard)).perform(click()) // Check is dismissed onView(withText(R.string.create_favorite_title)).check(doesNotExist()) + onView(withText(R.string.action_create)).check(doesNotExist()) + onView(withText(R.string.action_discard)).check(doesNotExist()) } @Test fun inCreateFavoriteDialogWhenUserClicksCreateWithNoCodeErrorMessageIsShown() { - // Show dialog + // Show error message onView(withId(R.id.fab)).perform(click()) // Be sure the code field is empty onView(withId(R.id.favorite_code_edit_text)).perform(clearText()) @@ -138,8 +152,8 @@ class FavoritesFragmentTest { // Dialog is not dismissed onView(withText(R.string.create_favorite_title)).check(matches(isDisplayed())) // Error message is shown - onView(withText(R.string.error_create_favorite_code_required)).check(matches(isDisplayed())) -// onView(allOf(withParent(R.id.txtPhoneNumber), withText("error text"))).check(matches(isDisplayed())) + val errorMessage = activityRule.activity.getString(R.string.error_create_favorite_code_required) + onView(withId(R.id.favorite_code_edit_text)).check(matches(hasErrorText(errorMessage))).check(matches(isDisplayed())) } @Test @@ -151,7 +165,7 @@ class FavoritesFragmentTest { // Start typing onView(withId(R.id.favorite_code_edit_text)).perform(typeText("1337")) // Check error message is hidden - onView(withText(R.string.error_create_favorite_code_required)).check(doesNotExist()) + onView(withId(R.id.favorite_code_edit_text)).check(matches(hasErrorText(nullValue(String::class.java)))) } @@ -160,14 +174,19 @@ class FavoritesFragmentTest { // When val resultsList = TestFactoryFavoriteView.generateFavoriteViewList() results.postValue(Resource.success(resultsList)) - // Then - onView(RecyclerViewMatcher.withRecyclerView(R.id.recycler_view).atPosition(0)).check(matches(hasDescendant(withText(resultsList[0].latestEta)))) - onView(RecyclerViewMatcher.withRecyclerView(R.id.recycler_view).atPosition(0)).check(matches(hasDescendant(withText(resultsList[0].code.toString())))) - // Assert - onView(withId(R.id.progress_bar)).check(doesNotExist()) + // Then check all items + for (favoriteView in resultsList.withIndex()) { + // Scroll to item INDEX + onView(withId(R.id.recycler_view)).perform(RecyclerViewActions.scrollToPosition(favoriteView.index)) + // Check item is displayed correctly + onView(RecyclerViewMatcher.withRecyclerView(R.id.recycler_view).atPosition(favoriteView.index)).check(matches(hasDescendant(withText(resultsList[favoriteView.index].code.toString())))) + onView(RecyclerViewMatcher.withRecyclerView(R.id.recycler_view).atPosition(favoriteView.index)).check(matches(hasDescendant(withText(resultsList[favoriteView.index].latestEta)))) + onView(RecyclerViewMatcher.withRecyclerView(R.id.recycler_view).atPosition(favoriteView.index)).check(matches(hasDescendant(withText(resultsList[favoriteView.index].originalText)))) + } } - } + +// https://spin.atomicobject.com/2016/04/15/espresso-testing-recyclerviews/ // https://spin.atomicobject.com/2016/04/15/espresso-testing-recyclerviews/ // https://medium.com/@_rpiel/recyclerview-and-espresso-a-complicated-story-3f6f4179652e \ No newline at end of file diff --git a/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/home/HomeActivityTest.kt b/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/home/HomeActivityTest.kt index b520e01..deb5432 100644 --- a/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/home/HomeActivityTest.kt +++ b/transport-eta-android/mobile-ui/src/androidTest/java/com/joaquimley/transporteta/ui/home/HomeActivityTest.kt @@ -44,4 +44,10 @@ class HomeActivityTest { // https://www.kotlindevelopment.com/runtime-permissions-espresso-done-right/ // https://developer.android.com/reference/android/support/test/rule/GrantPermissionRule.html + + // https://stackoverflow.com/questions/25998659/espresso-how-can-i-check-if-an-activity-is-launched-after-performing-a-certain + + // https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IntentsBasicSample + + // https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IntentsAdvancedSample } \ No newline at end of file diff --git a/transport-eta-android/mobile-ui/src/main/java/com/joaquimley/transporteta/ui/home/favorite/FavoritesFragment.kt b/transport-eta-android/mobile-ui/src/main/java/com/joaquimley/transporteta/ui/home/favorite/FavoritesFragment.kt index 204daf7..64aad82 100644 --- a/transport-eta-android/mobile-ui/src/main/java/com/joaquimley/transporteta/ui/home/favorite/FavoritesFragment.kt +++ b/transport-eta-android/mobile-ui/src/main/java/com/joaquimley/transporteta/ui/home/favorite/FavoritesFragment.kt @@ -29,6 +29,7 @@ import kotlinx.android.synthetic.main.fragment_favourites.* import kotlinx.android.synthetic.main.view_message.* import javax.inject.Inject + /** * Created by joaquimley on 24/03/2018. */ @@ -116,15 +117,17 @@ class FavoritesFragment : Fragment() { message_view.setVisible(false) if (isLoading) { if (swipe_refresh.isRefreshing.not() && adapter.isEmpty()) { - progress_bar.visibility = View.VISIBLE + progress_bar?.setVisible(true) } } else { - swipe_refresh.isRefreshing = false - progress_bar.visibility = View.GONE + swipe_refresh?.isRefreshing = false + progress_bar?.setVisible(false) } } private fun setupScreenForSuccess(favoriteViewList: List) { + swipe_refresh?.isRefreshing = false + progress_bar?.setVisible(false) message_view?.setVisible(false) recycler_view?.setVisible(true) adapter.submitList(favoriteViewList) @@ -141,13 +144,13 @@ class FavoritesFragment : Fragment() { private fun setupScreenForError(message: String?) { if (adapter.isEmpty()) { recycler_view?.setVisible(false) - message_view?.setVisible(true) - // TODO set the view to error state message_text_view?.text = message + message_view?.setVisible(true) } else { - view?.let { - // TODO -> Add retry button (TDD: still no tests) - Snackbar.make(it, message.toString(), Snackbar.LENGTH_LONG) + message?.let { + Snackbar.make(favorites_fragment_container, it, Snackbar.LENGTH_LONG) + .setAction(R.string.action_retry, { viewModel.retry() }) + .show() } } } diff --git a/transport-eta-android/mobile-ui/src/main/res/layout/fragment_favourites.xml b/transport-eta-android/mobile-ui/src/main/res/layout/fragment_favourites.xml index ac55461..2d1eab0 100644 --- a/transport-eta-android/mobile-ui/src/main/res/layout/fragment_favourites.xml +++ b/transport-eta-android/mobile-ui/src/main/res/layout/fragment_favourites.xml @@ -1,6 +1,7 @@ diff --git a/transport-eta-android/presentation/src/main/java/com/joaquimley/transporteta/presentation/home/favorite/FavoritesViewModelFactory.kt b/transport-eta-android/presentation/src/main/java/com/joaquimley/transporteta/presentation/home/favorite/FavoritesViewModelFactory.kt index 8e1e93c..aaa9175 100644 --- a/transport-eta-android/presentation/src/main/java/com/joaquimley/transporteta/presentation/home/favorite/FavoritesViewModelFactory.kt +++ b/transport-eta-android/presentation/src/main/java/com/joaquimley/transporteta/presentation/home/favorite/FavoritesViewModelFactory.kt @@ -7,7 +7,10 @@ import com.joaquimley.transporteta.sms.SmsController abstract class FavoritesViewModelFactory(private val smsController: SmsController) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return create() as T + if (modelClass is FavoritesViewModel) { + return create() as T + } + throw IllegalStateException("Wrong class type passed ${modelClass.name}") } fun create(): FavoritesViewModel {