How to use TDD for Android Integration Tests: Part 1

Discover how structured development using TDD and MVVM in Jetpack Compose creates robust TODO apps.
Dec 9 2021 · 4 min read

Introduction 

I’ve been busy coding without integration tests until I realized that not writing the integration tests was the reason I was busy.

This is part 1 of the ongoing series of articles where we keep adding new screens with more complexity.

Today we are going to write a simple TODO app, so simple that it will just have a TODO list but with 100% integration test coverage. As we are going to follow the TDD principle, we will write failing tests first and then actual code!

We will use Jetpack compose for views with MVVM architecture. For the sake of simplicity, we will not use HILT but it can be added anytime and that should not affect the things we are going to learn in this post.

If you haven’t read our previous articles about jetpack compose with dagger/hilt, MVVM, Navcontroller, and MVVM state management in a simple way using jetpack compose, I suggest checking them out.

We will divide this post into small chunks to make sure we stay focused.

We are what we repeatedly do. Excellence, then, is not an act, but a habit. Try out Justly and start building your habits today!

Let’s get started!

TODO app — Show a blank screen with “TODOs” title

Open android studio and create a new empty jetpack compose activity project. That will leave you with an app that has a “Hello android” greeting.

Let’s add a jetpack compose function to display an empty screen with “TODOs” title and replace the function in MainActivity to use it.

 

@Composable
fun TodoList() {
    
}

MainActivity onCreate

setContent {
    TodoTheme {
        Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
            TodoList()
        }
    }
}

Simple isn’t it? Now before we write code to display the title, let’s first write a test to verify that title is shown and make sure it fails.

Add TodoListTest file in androidTest directory. Make sure you don’t add it in test directory as we are not writing unit tests.

The initial state of the test file should look like this.

class TodoListTest {
    @Before
    fun setUp() {
        
    }

    @After
    fun tearDown() {}
}

Make sure you have the following dependencies added to your app module’s build.gradle file.

androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"

To test the screen on an actual emulator, we will need to use createComposeRule() as a rule that will launch the app with the compose view and will close the app once the test is done running.

Add this to the top of the class

@get:Rule
val composeTestRule = createComposeRule()

and then update setUp function to set compose view as compose rule content

public override fun setUp() {
    super.setUp()
    composeTestRule.setContent { TodoList() }
    composeTestRule.waitForIdle()
}

We are done with the basic setup, now it’s time to write the test.

Add the first test

@Test
fun testScreenTitleIsShown() {
    composeTestRule.onNodeWithText("TODOs").assertIsDisplayed()
}

Well, that’s it! The test is pretty self-explanatory. We are trying to find a node (View) with the text “TODOs” and verifying that it’s actually displayed on the screen.

You can right-click on the test and select Run 'testScreenTitleIsShown from the context menu, wait for the test to finish, and verify that it fails!

Now let’s update the code to make the test green!

@Composable
fun TodoList() {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(text = "TODOs", fontSize = 22.sp,
            modifier = Modifier.padding(vertical = 8.dp))
    }
}

Now run tests again and verify that it passes. This is one iteration of the TDD cycle. Every iteration should add a failing test first and later actual code to make that test pass.

TODO app — Show 3 TODO items

For simplicity, we will add a repository to provide 3 to-do items and use it here instead of fetching todos from API or DB.

Let’s start by adding Task class.

data class Task(val title: String)

and a repository interface that ViewModel will use to fetch tasks

interface TaskRepository {
    fun getTasks(): List<Task>
}

Let’s create an implementation that will provide 3 static tasks

class TaskRepositoryImpl : TaskRepository {
    override fun getTasks(): List<Task> {
        return listOf(
            Task("Hello TDD"),
            Task("This is fun"),
            Task("I can not live without TDD")
        )
    }
}

and TodoViewModel to manage the state for the view

class TodoViewModel(
    private val taskRepository: TaskRepository
) : ViewModel() {
    val tasks = MutableStateFlow(taskRepository.getTasks())
}

Now let’s update the view and activity to take ViewModel as an argument

val viewModel by viewModels<TodoViewModel>()

setContent {
    TodoTheme {
        // A surface container using the 'background' color from the theme
        Surface(
            modifier = Modifier.fillMaxSize(),
            color = MaterialTheme.colors.background
        ) {
            TodoList(viewModel)
        }
    }
}
@Composable
fun TodoList(viewModel: TodoViewModel) {
    val tasks by viewModel.tasks.collectAsState()
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "TODOs", fontSize = 22.sp,
            modifier = Modifier.padding(vertical = 8.dp)
        )
        TaskView(tasks = tasks )
    }
}

@Composable
fun TaskView(tasks: List<Task>) {

}

It’s time to write a failing test now, remember we need to write it before the actual code.

Add viewModel variable and update setUp function

private val viewModel = TodoViewModel(TaskRepositoryImpl())

@Before
fun setUp() {
    composeTestRule.setContent { TodoList(viewModel) }
    composeTestRule.waitForIdle()
}

and add the test

@Test
fun testTaskItemsAreShown() {
    composeTestRule.onNodeWithText("Hello TDD").assertIsDisplayed()
    composeTestRule.onNodeWithText("This is fun").assertIsDisplayed()
    composeTestRule.onNodeWithText("I can not live without TDD").assertIsDisplayed()
}

If you run the test, it will fail as expected. Now let’s update the code to show the task list.

@Composable
fun TaskView(tasks: List<Task>) {
    tasks.forEach {
        Text(text = it.title, fontSize = 16.sp, modifier = Modifier.padding(16.dp))
    }
}

That’s it. Run the tests again and verify that they all pass.

Run the tests.png

Hope you learned something useful!

Conclusion 

Structured development with TDD and MVVM in Jetpack Compose ensures robust, scalable TODO apps. While opting out of HILT for simplicity, our approach prioritizes functionality and smooth integration, demonstrating adherence to best practices for future-proofed, maintainable projects.

Happy coding!


jimmy image
Jimmy Sanghani
Jimmy Sanghani is a tech nomad and cofounder at Canopas helping businesses use new age leverage - Code and Media - to grow their revenue exponentially. With over a decade of experience in both web and mobile app development, he has helped 100+ clients transform their visions into impactful digital solutions. With his team, he's helping clients navigate the digital landscape and achieve their objectives, one successful project at a time.


jimmy image
Jimmy Sanghani
Jimmy Sanghani is a tech nomad and cofounder at Canopas helping businesses use new age leverage - Code and Media - to grow their revenue exponentially. With over a decade of experience in both web and mobile app development, he has helped 100+ clients transform their visions into impactful digital solutions. With his team, he's helping clients navigate the digital landscape and achieve their objectives, one successful project at a time.

contact-footer
Say Hello!
footer
Subscribe Here!
Follow us on
2024 Canopas Software LLP. All rights reserved.