How to Write Unit Tests for ViewModel in Jetpack Compose?

Emine İNAN
Huawei Developers
Published in
8 min readNov 1, 2023

--

Photo by Alessia Cocconi on Unsplash

Introduction 🌟

In this article, we will learn how to implement unit tests to test the ViewModel in Jetpack Compose. As a prerequisite, you need to be familiar with how to implement ViewModel in Jetpack Compose.

The Importance of Unit Testing in Android App Development

Writing unit tests is crucial for developing Android apps for several reasons. Why?

  • Unit tests ensure code quality by catching bugs and errors early on, saving time and resources in the long run.
  • Unit tests facilitate code maintenance by ensuring that new changes do not break existing functionality as the codebase evolves.
  • Unit tests improve code design by forcing developers to think about the code’s organization and structure.

Overall, unit tests are essential for developing high-quality, maintainable, and scalable Android apps.

Test the True False App

A simple true-false app built with Jetpack Compose that involves writing unit tests to test the ViewModel component.

The general flow in the app goes as follows:

💠 The user is asked a true-false question.
💠 The user marks the answer as correct or incorrect.
💠 The user’s response is updated.
💠 If the user answers correctly, his/her score increases.
💠 If the user answers incorrectly, his/her score does not change.
💠 The game ends when the user answers all the questions.
💠 The user can exit the game or play again.

True False App

To start testing the True False app, you can access the project’s starter code here. Let’s get started!

Adding Dependencies

Open the build.gradle.kts file and add the following dependency to the dependencies section of the file:

build.gradle.kts

Examining the ViewModel Class

Before writing unit tests for the GameViewModel class, let’s examine the class.

🚩 init{}: The init block is used to initialize the game. It calls the resetGame() function.

🚩 resetGame(): This function sets up the initial state of the game by resetting it and providing a new question for the user to answer.

It clears the usedQuestions list, which is used to keep track of the questions that have already been asked. Then calls the getQuestion() function to retrieve a new question, and updates the _uiState value with the new question and its correct answer.

🚩 getQuestion(): This function returns a Question object.

It selects a random question from a list of all available questions using the random() function. Then, it checks if the selected question has already been used before by checking if it exists in a list called usedQuestions. If the question has already been used, the function recursively calls itself to select another random question. If the selected question has not been used before, it is added to the usedQuestions list and returned as the current question.

🚩 updateUserAnswer(): This function is called to update the value of the userAnswer variable with the selection entered by the user.

🚩 checkUserAnswer(): This function checks the user’s answers and ensures that the score is updated if the answer given is correct.

It checks if the userAnswer matches the uiState.value.correctAnswer. If they match, the user’s score is updated by adding a constant value SCORE_INCREASE to the _uiSate.value.score. Then, it updates the UI state by setting the isAnswerWrong to false using the _uiSate.update() function. Finally, it calls another function called updateGameScore() to update the game score with the new value. If the user’s answer does not match the correct answer, it updates the UI state by setting the isAnswerWrong to true.

🚩 updateGameScore(): This function updates the user’s game score.

It checks if the size of the usedQuestions list is equal to MAX_NO_OF_QUESTIONS to determine if the game is over or not. If all questions have been used, the _uiState is updated using the update function. The isGameOver is set to true, and the score is updated with the updatedScore value. If there are still questions remaining, the _uiState is updated similarly, but without changing the isGameOver. Only the score is updated with the updatedScore.

🚩 goToNextQuestion(): This function moves the game to the next question by updating the UI state with the new question and answer.

It checks if the number of used questions is equal to the maximum number of questions. If it is, it means the game is over and the UI state is updated isGameOver to true. If the game is not over, it calls the getQuestion() function to retrieve the next question and answer. It then updates the UI state by setting the currentQuestion to the question, correctAnswer to the answer, and currentQuestionCount to the current question count plus one.

GameViewModel.kt

We examined in detail the functions in the GameViewModel class and what they do.

Writing Unit Tests

Key Characteristics of Effective Unit Tests

A good unit test should have four key characteristics:

💠 It should be focused on testing a specific unit of code, such as a class or method. This ensures that the test is narrow and only validates the correctness of individual pieces of code.

💠 It should be easily understandable, with the intention behind the test immediately clear to any developer who reads the code.

💠 It should be deterministic, consistently passing or failing regardless of how many times it is run without any code changes.

💠 It should be self-contained, running in isolation without requiring any human interaction or setup.

Now, let’s start writing unit tests taking these basic features into consideration.

Step 1

Create a class named GameViewModelTest to write a unit test for the GameViewModel class, and inside the GameViewModelTest class, create a gameViewModel property and assign an instance of the GameViewModel class to it.

GameViewModelTest.kt

Step 2

Create a gameViewModel_Initialization_FirstQuestionLoaded() method and annotate it with the @Test annotation.

This test ensures that when the gameViewModel is initialized, the initial gameUiState is set correctly with a non-empty currentQuestion and correctAnswer, a currentQuestionCount of 1, a score of 0, a null value for isAnswerWrong, and isGameOver set to false.

🚩 Create a gameUiState variable and assign gameViewModel.uiState.value to it.

🚩 Add assertFalse() functions to verify that the currentQuestion.isEmpty() property and correctAnswer.isEmpty() property, and isGameOver is set to false.

🚩 To verify the state is correct, add the assertTrue() functions to assert that the currentQuestionCount property is set to 1, the score property is set to 0, isAnswerWrong property is set to null.

GameViewModelTest.kt

Step 3

Create a gameViewModel_AnsweredCorrectly_ScoreUpdated() method and annotate it with the @Test annotation.

This test ensures that the GameViewModel class is correctly updating the score when the user answers a question correctly.

🚩 Create a gameUiState variable and assign gameViewModel.uiState.value to it.

🚩 Call the updateUserAnswer() function with the correct answer to simulate the user answering the question correctly.

🚩 Call the checkUserAnswer() function to check if the user’s answer is correct or not.

🚩 Update the value of gameUiState with the new value of gameViewModel.uiState.value.

🚩 Add the assertEquals() function to verify the state is correct, and to check the value of the gameUiState.isAnswerWrong property is equal to the value of the false and the value of the gameUiState.score property is equal to the value of the SCORE_INCREASE.

GameViewModelTest.kt

Step 4

Create a gameViewModel_AnsweredIncorrectly_ScoreNotUpdated() method and annotate it with the @Test annotation.

This test ensures that the GameViewModel class correctly handles incorrect user answers and does not update the user’s score in that case.

🚩 Define a variable named incorrectAnswer and assign it the value “and”.

🚩 Call the updateUserAnswer() function with the incorrect answer to simulate the user answering the question incorrectly.

🚩 Call the checkUserAnswer() function to check if the user’s answer is correct or not.

🚩 Create a gameUiState variable and assign gameViewModel.uiState.value to it.

🚩 Add the assertEquals() function to verify the state is correct, and to check the value of the gameUiState.isAnswerWrong property is equal to the value of the true and the value of the gameUiState.score property is equal to 0.

GameViewModelTest.kt

Step 5

Create a gameViewModel_AllQuestionSolved_UiStateUpdatedCorrectly() method and annotate it with the @Test annotation.

This test checks if the UI state of the gameViewModel is updated correctly when all the questions in the game have been solved.

🚩 Define a variable named expectedScore and assign it the value 0.

🚩 Create a gameUiState variable and assign gameViewModel.uiState.value to it.

🚩 Write a repeat loop to iterate MAX_NO_OF_QUESTIONS times, it simulates solving each question in the game.

🚩 Inside the loop, increment expectedScore by SCORE_INCREASE for each iteration.

🚩 Call the updateUserAnswer() function with the correct answer to simulate the user answering the question correctly.

🚩 Call the checkUserAnswer() function to check if the user’s answer is correct or not.

🚩 Call the goToNextQuestion() function to move to the next question.

🚩 Update the value of gameUiState with the new value of gameViewModel.uiState.value.

🚩 Add assertEquals() function to ensure that the expectedScore matches the score property of the gameUiState.

🚩 After the loop, add assertEquals() function to ensure that the MAX_NO_OF_QUESTIONS matches the currentQuestionCount property of the gameUiState. This means all questions have been solved.

🚩 Add assertTrue() function to ensure that the isGameOver property of the gameUiState is true. This means the game is over after all questions are solved.

GameViewModelTest.kt

Run Tests

We have completed all the unit tests we will write for the GameViewModel class.

Let’s press the Run test icon to run all the tests in the GameViewModelTest class and see the results.

Run Tests

You can see the test results below.

Test Results

All the unit tests we wrote passed!

Conclusion

Unit testing has several pros for software development. It helps to detect defects early, improves code quality, provides faster feedback, makes debugging easier, increases developer confidence, facilitates refactoring, and supports agile development. To benefit from these pros, do not forget to write unit tests while developing Android apps.

--

--

Emine İNAN
Huawei Developers

Android Developer by day, Android Developer by night. @Huawei