oligazar
7/14/2018 - 7:08 AM

Testing instructions

1#. Google I/O 2018 app — Architecture and Testing https://medium.com/androiddevelopers/google-i-o-2018-app-architecture-and-testing-f546e37fc7eb

2#. The Basics of Android Espresso Testing: Activities & Fragments https://android.jlelse.eu/the-basics-of-android-espresso-testing-activities-fragments-7a8bfbc16dc5

3#. Links

4#. Instrumentation tests

5#. Gradle setup

The Android Testing Support library (ATSL) provides a great framework for testing your Android app. It has a JUnit 4-compatible test runner (AndroidJUnitRunner) and functional UI testing through Espresso and UI Automator. You can run tests for these APIs directly in the Android Studio IDE or from the command line, which makes it very easy to integrate them into your development flow.

Source code: $ git clone https://github.com/googlecodelabs/android-testing

Creating presenter with flavor based injection

@Override
   public void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);

       mListAdapter = new NotesAdapter(new ArrayList<Note>(0), mItemListener);

       mActionsListener = new NotesPresenter(Injection.provideNotesRepository(), this);

   }

Flavors

```gradle

flavorDimensions "client", "server"
productFlavors {
    prod {
        dimension "client"
        applicationIdSuffix ".prod"
    }
    mock {
        dimension "client"
        applicationIdSuffix ".mock"
    }
    // Flavor Dimensions allow us to define groups of flavors. Gradle creates every combination between these groups for us. No need to mix and match by hand.
    dev {
        dimension "server"
    }
    production {
        dimension "server"
    }
}

Kotlin default final classes/methods

  • Add the open keyword to classes and methods that you’ll mock.
  • Create an interface and have the class implement the interface. Then, just mock the interface (interfaces are open by default).
  • Use mock-maker-inline. You’ll do this later.
class VictoryViewModelTest {

    //  to avoid the check of setting values to LiveData objects on threads that are not the Android main thread
    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()

    private val viewStateObserver = mock<Observer<VictoryUiModel>>()
    private val mockVictoryRepository: VictoryRepository = mock()
    private val viewModel = VictoryViewModel()

    @Before
    fun setUpTaskDetailViewModel() {
        viewModel.viewState.observeForever(viewStateObserver)
        viewModel.repository = mockVictoryRepository
    }

    @Test
    fun incrementVictoryCount_CallsRepository() {
        stubVictoryRepositoryGetVictoryCount(5) // Arrange
        viewModel.incrementVictoryCount() // Act
        verify(mockVictoryRepository).getVictoryCount() // Assert
    }

    @Test
    fun incrementVictoryCount_UpdatesCount() {
        val previousCount = 5
        stubVictoryRepositoryGetVictoryCount(previousCount)
        viewModel.incrementVictoryCount()
        verify(mockVictoryRepository).setVictoryCount(previousCount + 1)
    }

    @Test
    fun incrementVictoryCount_ReturnsUpdatedCount() {
        val previousCount = 5
        stubVictoryRepositoryGetVictoryCount(previousCount)
        viewModel.incrementVictoryCount()
        verify(viewStateObserver)
                .onChanged(VictoryUiModel.CountUpdated(previousCount + 1))
    }


    @Test
    fun setVictoryTitle_SavesTitle() {
        val title = "New title"
        viewModel.setVictoryTitle(title)

        verify(mockVictoryRepository).setVictoryTitle(title)
    }

    @Test
    fun setVictoryTitle_ReturnsTitle() {
        val title = "New title"
        viewModel.setVictoryTitle(title)

        verify(viewStateObserver).onChanged(VictoryUiModel.TitleUpdated(title))
    }

    private fun stubVictoryRepositoryGetVictoryTitleAndCount(titleAndCount: Pair<String, Int>) {
        stubVictoryRepositoryGetVictoryTitle(titleAndCount.first)
        stubVictoryRepositoryGetVictoryCount(titleAndCount.second)
        whenever(mockVictoryRepository.getVictoryTitleAndCount())
                .thenReturn(titleAndCount)
    }

    private fun stubVictoryRepositoryGetVictoryTitle(title: String) {
        whenever(mockVictoryRepository.getVictoryTitle())
                .thenReturn(title)
    }

    private fun stubVictoryRepositoryGetVictoryCount(count: Int) {
        whenever(mockVictoryRepository.getVictoryCount())
                .thenReturn(count)
    }
}

  testImplementation 'junit:junit:4.12'
  androidTestImplementation "android.arch.core:core-testing:$lifecycle_version"
  androidTestImplementation "com.nhaarman:mockito-kotlin:1.6.0"
  androidTestImplementation "org.mockito:mockito-android:2.23.0"
  androidTestImplementation 'com.android.support.test:runner:1.0.2'
  androidTestImplementation 'com.android.support.test:rules:1.0.2'
  androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
class SearchResultsTest {

    private lateinit var repository: RecipeRepository
    private lateinit var presenter: SearchResultsPresenter
    private lateinit var view: SearchResultsPresenter.View

    @Before
    fun setup() {
        repository = mock()
        view = mock()
        presenter = SearchResultsPresenter(repository)
        presenter.attachView(view)
    }

    @Test
    fun `search calls showLoading`() {
        presenter.search("eggs")

        verify(view).showLoading()
    }

    @Test
    fun `search calls getRecipes`() {
        presenter.search("eggs")

        verify(repository).getRecipes(eq("eggs"), any())
    }

    @Test
    fun `search withRepositoryHavingRecipes calls ShowRecipes`() {
        // setup
        val recipe = Recipe("id", "title", "imageUrl", "sourceUrl", false)
        val recipes = listOf(recipe)

        doAnswer {
            val callback: RepositoryCallback<List<Recipe>> = it.getArgument(1)
            callback.onSuccess(recipes)
        }.whenever(repository).getRecipes(eq("eggs"), any())

        // invoke
        presenter.search("eggs")

        // verify
        verify(view).showRecipes(eq(recipes))
    }

    @Test
    fun `addFavorite should updateRecipeStatus`() {
        // setup
        val recipe = Recipe("id", "title", "imageUrl", "sourceUrl", false)

        // invoke
        presenter.addFavorite(recipe)

        // verify
        Assert.assertTrue(recipe.isFavorited)
    }
}


class VictoryViewModelTest {

    //  to avoid the check of setting values to LiveData objects on threads that are not the Android main thread
    @get:Rule
    var instantTaskExecutorRule = InstantTaskExecutorRule()

    private val viewStateObserver = mock<Observer<VictoryUiModel>>()
    private val mockVictoryRepository: VictoryRepository = mock()
    private val viewModel = VictoryViewModel()

    @Before
    fun setUpTaskDetailViewModel() {
        viewModel.viewState.observeForever(viewStateObserver)
        viewModel.repository = mockVictoryRepository
    }

    @Test
    fun initialize_ReturnsTitle() {
        val title = "New title"
        val count = 5
        stubVictoryRepositoryGetVictoryTitleAndCount(Pair(title, count))

        viewModel.initialize()

        verify(viewStateObserver).onChanged(VictoryUiModel.TitleUpdated(title))
    }

    @Test
    fun initialize_ReturnsCount() {
        val title = "New title"
        val count = 5
        stubVictoryRepositoryGetVictoryTitleAndCount(Pair(title, count))

        viewModel.initialize()

        verify(viewStateObserver).onChanged(VictoryUiModel.CountUpdated(count))
    }

    @Test
    fun incrementVictoryCount_CallsRepository() {
        stubVictoryRepositoryGetVictoryCount(5) // Arrange
        viewModel.incrementVictoryCount() // Act
        verify(mockVictoryRepository).getVictoryCount() // Assert
    }

    @Test
    fun incrementVictoryCount_UpdatesCount() {
        val previousCount = 5
        stubVictoryRepositoryGetVictoryCount(previousCount)
        viewModel.incrementVictoryCount()
        verify(mockVictoryRepository).setVictoryCount(previousCount + 1)
    }

    @Test
    fun incrementVictoryCount_ReturnsUpdatedCount() {
        val previousCount = 5
        stubVictoryRepositoryGetVictoryCount(previousCount)
        viewModel.incrementVictoryCount()
        verify(viewStateObserver)
                .onChanged(VictoryUiModel.CountUpdated(previousCount + 1))
    }


    @Test
    fun setVictoryTitle_SavesTitle() {
        val title = "New title"
        viewModel.setVictoryTitle(title)

        verify(mockVictoryRepository).setVictoryTitle(title)
    }

    @Test
    fun setVictoryTitle_ReturnsTitle() {
        val title = "New title"
        viewModel.setVictoryTitle(title)

        verify(viewStateObserver).onChanged(VictoryUiModel.TitleUpdated(title))
    }

    private fun stubVictoryRepositoryGetVictoryTitleAndCount(titleAndCount: Pair<String, Int>) {
        stubVictoryRepositoryGetVictoryTitle(titleAndCount.first)
        stubVictoryRepositoryGetVictoryCount(titleAndCount.second)
        whenever(mockVictoryRepository.getVictoryTitleAndCount())
                .thenReturn(titleAndCount)
    }

    private fun stubVictoryRepositoryGetVictoryTitle(title: String) {
        whenever(mockVictoryRepository.getVictoryTitle())
                .thenReturn(title)
    }

    private fun stubVictoryRepositoryGetVictoryCount(count: Int) {
        whenever(mockVictoryRepository.getVictoryCount())
                .thenReturn(count)
    }
}
@LargeTest
class MainActivityTest {

  @Rule
  @JvmField
  var rule = ActivityTestRule(MainActivity::class.java)

  @Test
  fun tappingOnTitle_opensEditDialog() {
    onView(withId(R.id.textVictoryTitle))
        .perform(click())

    onView(withId(R.id.alertTitle))
        .check(matches(isDisplayed()))

    onView(withId(android.R.id.button2))
        .perform(click())
  }

  @Test
  fun editingTitle_DoesntChangeCount() {
    onView(withId(R.id.fab))
            .perform(click())
    onView(withId(R.id.textVictoryTitle))
            .perform(click())
    val newTitle = "Made the bed"
    onView(instanceOf(EditText::class.java))
            .perform(clearText())
            .perform(typeText(newTitle))
    onView(withText(R.string.dialog_ok))
            .perform(click())

    onView(allOf(withId(R.id.tvVictoryCount), withText("0")))
            .check(doesNotExist())
  }

  @Test
  fun incrementingVictoryCount_updatesCountView() {

    val previousCountString = rule.activity.tvVictoryCount.text.toString()
    val previousCount =
            if (previousCountString.isBlank()) {
              0
            } else {
              previousCountString.toInt()
            }

    onView(withId(R.id.fab))
            .perform(click())

    onView(allOf(withId(R.id.tvVictoryCount),
                 withText((previousCount + 1).toString())))
            .check(matches(isDisplayed()))
  }

  @Test
  fun editingDialog_updatesTitle() {
    onView(withId(R.id.textVictoryTitle))
        .perform(click())

    val newTitle = "Made the bed"
    onView(instanceOf(EditText::class.java))
        .perform(clearText())
        .perform(typeText(newTitle))

    onView(withText(R.string.dialog_ok))
        .perform(click())

    onView(allOf(withId(R.id.textVictoryTitle), withText(newTitle)))
        .check(matches(isDisplayed()))
  }
}