Skip to main content
Android Development with Kotlin
CHAPTER 26 Beginner

Unit Testing and UI Testing

Updated: May 16, 2026
25 min read

# CHAPTER 26

Unit Testing and UI Testing

1. Introduction

Imagine building a 50-screen e-commerce application. You change a single line of code in the "Cart" logic. How do you know you didn't accidentally break the "Checkout" screen? Do you manually click through the entire app every time you save a file? No. Professional engineers rely on Automated Testing. You write code that tests your code. If you make a mistake, your tests will instantly fail and warn you *before* you ship the bug to millions of users. In this chapter, we will master Unit Testing (testing isolated logic) using JUnit and Mockito, and UI Testing (testing button clicks and screens) using Espresso.

2. Learning Objectives

By the end of this chapter, you will be able to:
  • Differentiate between the test directory (Unit Tests) and androidTest directory (UI Tests).
  • Write fundamental Unit Tests using the JUnit framework.
  • Understand the concept of Mocking utilizing the Mockito library.
  • Automate user interactions (clicks, typing) utilizing the Espresso UI framework.
  • Execute test suites natively within Android Studio.

3. The Testing Pyramid

Automated testing is traditionally visualized as a pyramid:
  • Unit Tests (Base): Fast, isolated tests targeting single functions. You should have hundreds of these. They run locally on your laptop's JVM (Java Virtual Machine), not on a phone emulator, so they execute in milliseconds.
  • Integration Tests (Middle): Testing how two components work together (e.g., Does the Repository correctly parse the Retrofit JSON?).
  • UI / End-to-End Tests (Top): Slow, heavy tests that actually boot up an Android Emulator, launch the app, click buttons, and verify the screen output. You should have fewer of these.

4. Directory Structure

Open your Android Studio Project pane. Inside src, you will see three folders:
  1. 1. main: Your actual app code.
  1. 2. test: Local Unit Tests (Runs on your computer's CPU).
  1. 3. androidTest: Instrumented UI Tests (Requires an Android Emulator or physical device).

5. Step 1: Writing a Unit Test (JUnit)

Let's test a simple helper function. Imagine a class that validates passwords. *(Create this inside src/main/java/com/example/myapp/PasswordValidator.kt)*
kotlin
1234567
class PasswordValidator {
    fun isValid(password: String): Boolean {
        if (password.length < 6) return false
        if (!password.any { it.isDigit() }) return false // Must contain a number
        return true
    }
}

Now, let's write the test! Right-click PasswordValidator, select Generate -> Test, and place it in the test directory. *(Inside src/test/java/.../PasswordValidatorTest.kt)*

kotlin
12345678910111213141516171819202122232425262728293031323334
import org.junit.Assert.assertEquals
import org.junit.Test

class PasswordValidatorTest {

    private val validator = PasswordValidator()

    @Test // The @Test annotation tells Android Studio this is a test function!
    fun `password too short returns false`() {
        // 1. Given (The setup)
        val shortPassword = "abc"

        // 2. When (The action)
        val result = validator.isValid(shortPassword)

        // 3. Then (The assertion/check)
        // We ASSERT that the 'result' is EQUAL to 'false'
        assertEquals(false, result)
    }

    @Test
    fun `password with no numbers returns false`() {
        val noNumberPassword = "password"
        val result = validator.isValid(noNumberPassword)
        assertEquals(false, result)
    }

    @Test
    fun `valid password returns true`() {
        val validPassword = "password123"
        val result = validator.isValid(validPassword)
        assertEquals(true, result)
    }
}

*Run the test:* Click the green "Play" button next to the class name. It will execute in 100 milliseconds and show a beautiful green checkmark!

6. Mocking (Mockito)

Unit tests must be *isolated*. If you are testing a ViewModel, you don't want it to actually connect to the real internet or the real Room database! We use Mockito to create "Fake" (Mock) versions of our Repositories.
kotlin
1234
// Example Mocking Setup:
// val mockRepository = mock(UserRepository::class.java)
// "When the viewmodel asks the mock repo for data, just return this fake string!"
// `when`(mockRepository.getUserName()).thenReturn("Fake John Doe")

*(Mockito configuration is complex and often requires additional Coroutine Test dispatchers for MVVM).*

7. Step 2: Writing a UI Test (Espresso)

UI Tests live in the androidTest folder. They boot the app and simulate a human finger clicking the screen. Espresso relies on three core actions: ViewMatchers (Find the button), ViewActions (Click the button), and ViewAssertions (Check the screen).

*(Inside src/androidTest/java/.../MainActivityTest.kt)*

kotlin
1234567891011121314151617181920212223242526272829303132
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.typeText
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class MainActivityTest {

    // 1. Tell Espresso which Activity to launch!
    @get:Rule
    val activityRule = ActivityScenarioRule(MainActivity::class.java)

    @Test
    fun loginFlow_updatesTextView() {
        
        // 2. Find the EditText, type "John", and close the keyboard
        onView(withId(R.id.nameEditText)).perform(typeText("John"))
        
        // 3. Find the Login Button, and click it!
        onView(withId(R.id.loginButton)).perform(click())

        // 4. Find the Welcome TextView, and ASSERT that its text changed to "Hello John"
        onView(withId(R.id.welcomeTextView)).check(matches(withText("Hello John")))
    }
}

*Run the test:* It will launch the Android Emulator, the app will open, you will see a "Ghost" type the text and click the button automatically, and the test will pass!

8. Common Mistakes

  • Putting UI Tests in the test folder: If you accidentally put an Espresso test in the local test folder, it will crash with a RuntimeException: Method not mocked. The local JVM does not have an Android UI!
  • Flaky UI Tests: UI tests are notoriously "flaky" (sometimes they pass, sometimes they fail). This happens if a network animation takes 2 seconds, but Espresso checks the screen after 1 second. You must use specialized Espresso "Idling Resources" to tell the test to wait for the animation to finish.

9. Best Practices

  • Test-Driven Development (TDD): A popular engineering philosophy where you write the Test *before* you write the actual code. The test fails initially. Then, you write the minimal code required to make the test pass. This ensures 100% test coverage.

10. Exercises

  1. 1. Create a MathHelper.kt file with an add(a: Int, b: Int) and multiply(a: Int, b: Int) function.
  1. 2. Generate a JUnit test class in the test directory. Write three tests asserting that the math logic is perfectly accurate.

11. Coding Challenges

Challenge: Build a basic UI with a TextView showing "0" and a Button saying "Increment". Write an Espresso UI test that launches the Activity, clicks the button 5 times in a row, and asserts that the TextView text exactly matches "5".

12. MCQ Quiz with Answers

Question 1

What is the fundamental operational difference between tests residing in the src/test/ directory versus the src/androidTest/ directory?

Question 2

Within the Espresso UI Testing framework, what is the sequence of the core testing API required to simulate a user typing into an input field?

13. Interview Questions

  • Q: Explain the structural concept of "Mocking" utilizing frameworks like Mockito. Why is it architecturally dangerous to allow a Unit Test to execute live Retrofit network calls?
  • Q: Detail the anatomy of a JUnit Unit Test. Explain the operational flow of the "Given, When, Then" (Arrange, Act, Assert) testing paradigm.
  • Q: Contrast the execution speed and maintenance overhead of Unit Tests versus Instrumented End-to-End (UI) Tests. Why does the "Testing Pyramid" recommend a vast foundation of Unit Tests and a minimal capstone of UI Tests?

14. FAQs

Q: I have an existing app with 50,000 lines of code. Do I need to stop development and write tests for everything? A: No! That is overwhelming. The industry standard is to test *new* code as you write it, and to write tests for existing code *only* when a bug is reported in that specific area. Gradually, your coverage will increase.

15. Summary

In Chapter 26, we fortified our application architecture through the rigor of Automated Testing. We distinguished between isolated JVM execution and Instrumented device execution. We authored foundational Unit Tests via the JUnit framework, adhering to the "Given-When-Then" paradigm to validate pure business logic. Furthermore, we automated complex user interaction matrices utilizing the Espresso UI testing suite. Our application is now mathematically verifiable, heavily mitigating the risk of regressions during future feature integration.

16. Next Chapter Recommendation

Our app is tested and works perfectly while open. But what if we need to upload a massive 4K video while the user minimizes the app to browse Instagram? If we rely on Coroutines, Android will kill our upload. Proceed to Chapter 27: Background Processing (WorkManager) to master guaranteed background execution.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·