Testing Android Applications – Unit and UI Testing in Android - Textnotes

Testing Android Applications – Unit and UI Testing in Android


Learn how to write Unit Tests using JUnit and Mockito and perform UI Testing with Espresso and UI Automator to ensure the reliability and functionality of your Android app.

Testing is an essential part of Android development. It ensures that your app behaves as expected and helps to identify bugs early in the development process. Android provides tools for both Unit Testing and UI Testing, which can significantly improve the quality of your app.

In this tutorial, we will cover Unit Testing using JUnit and Mockito and UI Testing using Espresso and UI Automator.

i) Unit Testing with JUnit and Mockito

1. What is Unit Testing?

Unit testing involves testing individual components (or units) of a program to ensure that they function as expected. In Android, this typically means testing individual methods or functions of your app’s logic, such as calculating values, processing user input, or handling network responses.

2. Setting Up JUnit for Unit Testing

JUnit is the standard testing framework used in Java and Android for unit testing. It allows you to write tests for individual components of your app, such as classes, methods, and logic.

Step 1: Adding Dependencies for JUnit

In your app-level build.gradle file, add the following dependencies:


dependencies {
testImplementation 'junit:junit:4.13.2' // JUnit dependency
testImplementation 'org.mockito:mockito-core:3.11.2' // Mockito dependency for mocking
}
Step 2: Writing Unit Tests with JUnit

JUnit tests are written in test classes, and each test method is annotated with @Test. Let’s write a simple unit test for a method that adds two numbers.


// Simple method to add two numbers
fun add(a: Int, b: Int): Int {
return a + b
}

// Unit test for the add method
import org.junit.Assert.*
import org.junit.Test

class CalculatorTest {

@Test
fun testAdd() {
val result = add(2, 3)
assertEquals(5, result)
}
}

In the above code:

  1. @Test marks the method as a unit test.
  2. assertEquals() checks if the expected value (5) matches the actual result from the add() function.
Step 3: Mocking Dependencies with Mockito

In many cases, the method you want to test depends on external components, such as a network service or database. Mockito allows you to mock these dependencies, ensuring that your tests focus on the logic you're testing, not on external factors.

For example, let’s say you have a method that fetches data from a remote server:


class DataFetcher(private val apiService: ApiService) {

fun fetchData(): String {
return apiService.getDataFromServer()
}
}

To test this method, we can mock the ApiService using Mockito:


import org.junit.Test
import org.mockito.Mockito.*
import org.junit.Assert.*

class DataFetcherTest {

@Test
fun testFetchData() {
// Mocking the ApiService
val mockApiService = mock(ApiService::class.java)
`when`(mockApiService.getDataFromServer()).thenReturn("Mock Data")

val dataFetcher = DataFetcher(mockApiService)

// Testing the method
val result = dataFetcher.fetchData()
assertEquals("Mock Data", result)
}
}

Here, Mockito is used to mock the ApiService, and we define that the method getDataFromServer() should return "Mock Data". This isolates the unit test from the real ApiService implementation.

ii) UI Testing with Espresso and UI Automator

1. What is UI Testing?

UI testing ensures that the user interface of your app behaves correctly. This includes testing user interactions like button clicks, form submissions, scrolling, etc. Espresso and UI Automator are two popular libraries for UI testing in Android.

2. Setting Up Espresso for UI Testing

Espresso is a UI testing framework provided by Google. It allows you to simulate user actions and verify that the UI responds correctly.

Step 1: Adding Dependencies for Espresso

In your app-level build.gradle file, add the following dependencies:


dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' // Espresso dependency
androidTestImplementation 'androidx.test.ext:junit:1.1.3' // JUnit for testing
androidTestImplementation 'androidx.test:runner:1.4.0' // Android test runner
}
Step 2: Writing Espresso Tests

Let’s say you have a simple button that, when clicked, updates a TextView. You can test this functionality using Espresso.


<!-- layout.xml -->
<Button
android:id="@+id/button"
android:text="Click me"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<TextView
android:id="@+id/textView"
android:text="Hello World"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

Now, you can write a test to verify that clicking the button updates the text in the TextView:


import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class MainActivityTest {

@Test
fun testButtonClickUpdatesTextView() {
// Perform a click on the button
onView(withId(R.id.button)).perform(click())

// Check if the TextView text has been updated
onView(withId(R.id.textView)).check(matches(withText("Button Clicked")))
}
}

In the test above:

  1. onView(withId(R.id.button)) finds the button by its ID.
  2. perform(click()) simulates a button click.
  3. onView(withId(R.id.textView)) finds the TextView by its ID.
  4. check(matches(withText("Button Clicked"))) verifies that the text in the TextView has changed after the button click.

3. Using UI Automator for Inter-App Testing

UI Automator is used for testing interactions between multiple apps or testing system-level interactions (like opening the settings app or interacting with notifications).

Step 1: Adding UI Automator Dependencies

In your app-level build.gradle file, add:


dependencies {
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
}
Step 2: Writing UI Automator Test

Let’s write a UI Automator test that interacts with the Settings app:


import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiSelector
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Test

class SettingsTest {

@Test
fun testOpenSettings() {
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())

// Open the settings app
device.pressHome()
device.findObject(UiSelector().description("Apps")).click()

// Find and click on the "Wi-Fi" setting
val wifiSetting: UiObject = device.findObject(UiSelector().text("Wi-Fi"))
wifiSetting.click()
// Check if Wi-Fi settings page is displayed
val wifiText: UiObject = device.findObject(By.text("Wi-Fi"))
assert(wifiText.exists())
}
}

In this test:

  1. UiDevice.getInstance() gets the device instance.
  2. UiSelector() is used to find UI elements by text, description, or other properties.
  3. pressHome() simulates pressing the home button.
  4. click() simulates a click on a UI element.

iii) Conclusion

Testing is an essential part of Android development that ensures your app works correctly. Unit testing helps ensure that your logic works as expected, while UI testing ensures that the app’s user interface behaves correctly during interactions.

  1. Unit Testing: Use JUnit and Mockito for testing methods and mocking dependencies in isolation.
  2. UI Testing: Use Espresso for testing interactions within your app, and UI Automator for cross-app or system-level tests.

By incorporating both unit and UI testing into your development process, you ensure that your app is both functional and user-friendly, and that it remains reliable even as it evolves over time.