Write Your First Unit Test in Android Using JUnit4 and Truth Assertion Library

Let’s be honest. Writing test cases in software development is either religiously followed or very much overlooked. There is no middle ground in it. But, whatever the case, it is an essential skill needed for a software developer. You will need to write test cases. If not today, then tomorrow. I’ll give a small introduction about the TDD architecture and testing in general before we start working on an example.
TDD architecture
You must have heard the term TDD a lot in recent times. TDD stands for test-driven development. As the name suggests, you will develop your software based on writing test cases.

Imagine you are working on a new feature for your app. Firstly, we will give it a thought before working on it to guarantee that we are not missing any edge cases. The problem with that approach is that we can overlook a few edge cases, and we will be able to run the code only in a running app. As more and more features get added, testing will be difficult and more time consuming than ever. The TDD approach is an excellent strategy to solve this issue.
As the image suggests, you will write the test case first before you write the actual function. If you are not familiar with this approach, it will be very confusing and incomprehensible. Imagine you write a test case for a method that doesn’t exist. That is the beauty of the TDD architecture. Each of these methods is called units. Writing test cases and testing those units are called unit testing. Your unit test must include all the possible interactions with the unit, including the standard interactions, invalid inputs, and cases where resources aren’t available.
Testing pyramid

The testing pyramid will classify the different types of tests according to their importance and granularity. In Android, there are three types of tests.
- UI tests ( Large tests ) ( Fidelity is excellent, larger execution time, hard to maintain )
- Integration tests ( Medium tests ) ( Fidelity is good, medium execution time, less difficult to maintain )
- Unit tests ( Small tests ) ( Fidelity is low, faster execution time, Easy to maintain )
As you can understand, unit tests are easy to implement, and UI tests and integration tests will take more time and maintenance. Generally, we will split the categories like the following: 70 percent small, 20 percent medium, and 10 percent large.
Unit testing in Android
Unit tests are the fundamental tests in your app testing strategy. By creating and running unit tests against your code, you can easily verify that the logic of individual units is correct. Running unit tests after every build helps you to quickly catch and fix software regressions introduced by code changes to your app.
For testing android apps, there are two types of automated test units available.
Local tests: The reason they are known as local tests is that they will be running on your local machine. In our case, our laptop or PC. They run on the local JVM. They don’t require either an emulator or physical android device to run. They are faster in execution, but provides less fidelity.
Instrumented tests: They will run only on physical or emulator devices. Instrumented tests provide more fidelity than local tests, but it is slower in execution. Therefore, it is recommended to do an instrumented test, only if you must test against the behavior of a real device.
I think now is an excellent time to take a deep breath and read everything one more time. Drink some water or coffee and come back. We are going to create a new project and then run our first unit test.
Create a project
Create a new project and go to the app’s build gradle file.
// Testing
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
The three dependencies mentioned above will be already added to your project by default. You can see that there are two types of dependencies added. ‘testImplementation’ and ‘androidTestImplementation’. The dependencies related to local tests will come under testImplementation and those that related to instrumentation tests will come under androidTestImplementation. Let’s now examine these dependencies.
testImplementation 'junit:junit:4.13.1': JUnit is the most popular and widely used testing framework for java. Integrating this dependency will allow us to write test-cases more cleaner and flexibly.
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0': The espresso testing framework is an instrumentation based API and will help us to test user interactions to avoid unexpected results. Since we are only talking about local unit tests today, we don’t have to worry about espresso at the moment.
The basic principle of testing is that, we have to assert if the actual result and the expected result are either true
or false
. In order to help us to check this scenario, which is called an “assertion”, we have a simple and beautiful library called, truth from Google. JUnit can also do assertions. But, truth can do it much better. I felt like it is much easier to read and has less boilerplate code. Let’s integrate truth dependency now.
testImplementation "com.google.truth:truth:1.1"
Your build gradle ( app ) will look like this now,
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.clintpauldev.unittestingsample"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
// Testing
testImplementation 'junit:junit:4.13.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// Assertion
testImplementation "com.google.truth:truth:1.1"
}
We are going to create a test-case for a function which validates registration inputs such as username, password and confirm password. We will try to include as many as possible edge cases. Such as,
- The input is not valid, if the username or password is empty.
- The username is already taken.
- The confirmed password is not same as the real password.
- The password contains less than two digits ( Optional ).
Create a new object class called RegistrationUtil and add a new function inside it, validateRegistrationInput() which contains three parameters and return a boolean. The parameters are user name, password and confirm password. Since, we don’t have a running dB, I’m using a static list of data for this example.
object RegistrationUtil {
private val existingUsers = listOf("Peter", "Mathew")
/**
* the input is not valid if
* the username/password is empty
* the username is already taken
* the confirmed password is not the same as real password
* the password contains less than 2 digits
* */
fun validateRegistrationInput(
userName: String,
password: String,
confirmPassword: String
) : Boolean {
return true
}
}
Now, don’t think about adding the logic to the function. Instead, let us create a new test class for this function.
- Right click on the object name
RegistrationUtil
and click on Generate. - Click on the test button

- Make sure to select testing library as JUnit 4 and click on OK.

- Choose the destination directory as ‘test’ and click OK.

Congrats. You have created a new testing class for your RegistrationUtil
class.
Now, let us write the test case for the following condition.
- The input is not valid, if the username or password is empty.
@Test
fun `empty username returns false`() {
}
Create a new function called empty username returns false
inside the RegistrationUtil
class. You might have noticed that a backtick ( ` ) is added before and after the function name. The compiler will convert the space inside the backtick into underscores. It will help in the readability of the test class. Also, notice that we have added an annotation called @Test
before the function name. This annotation will tell the JUnit that it must run as a test case.
Next, we should create a new object of the validateRegistrationInput()
and pass the username as empty. This test case must return a false
value since the username is empty.
@Test
fun `empty username returns false`() {
val result = RegistrationUtil.validateRegistrationInput(
userName = "",
password = "123",
confirmPassword = "123"
)}
Remember what I said earlier about the assertion? Now, we will have to assert if the result we got is true
or false
. We can use the help of Google truth library for that.
@Test
fun `empty username returns false`() {
val result = RegistrationUtil.validateRegistrationInput(
userName = "",
password = "123",
confirmPassword = "123"
)
assertThat(result).isFalse()}
Let’s assert that the result is false indeed. Make sure you are importing the correct dependency for the assertion.
import com.google.common.truth.Truth.assertThat
Now, click on the play button on the left side of the function name.

The test will obviously fail. Since, we are returning a true
value by default in the validateRegistrationInput()
function. But it is good practice to check the details of error message showing in the android studio. It will help us to know which test case failed, what was the reason, additional description etc.
It says the error occurred inside the RegistrationUtilTest
class. Inside that, the empty username returns false
function failed.

Also, additional error description showing that the test case expected the result to be false
instead the result was true
.

Now take a few minutes time to read and understand what we have discussed so far. After that, try to write the test-cases for the remaining conditions and run it. I’ll anyways add the remaining test cases here for you to crosscheck. But it is always better if you are trying it yourself and check how much you have learned.
- Valid username and correctly repeated password returns true
@Test
fun `valid username and correctly repeated password returns true`() {
val result = RegistrationUtil.validateRegistrationInput(
"clint",
"123",
"123"
)
assertThat(result).isTrue()
}
- Username already exists returns false
@Test
fun `username already exists returns false`() {
val result = RegistrationUtil.validateRegistrationInput(
"Peter",
"123",
"123"
)
assertThat(result).isFalse()
}
- Empty password returns false
@Test
fun `empty password returns false`() {
val result = RegistrationUtil.validateRegistrationInput(
"Peter",
"",
""
)
assertThat(result).isFalse()
}
- Password repeated incorrectly returns false
@Test
fun `password repeated incorrectly returns false`() {
val result = RegistrationUtil.validateRegistrationInput(
"Peter",
"123",
"1234"
)
assertThat(result).isFalse()
}
- Password contains less than 2 digits returns false
@Test
fun `password contains less than 2 digits returns false`() {
val result = RegistrationUtil.validateRegistrationInput(
"Peter",
"1abcd",
"1abcd"
)
assertThat(result).isFalse()
}
I hope you were able to write all the above test cases. Now, run all the tests at once by clicking on the play button on left of the class name. As expected, out of the 6 test cases, only 1 will be passed. Now, let’s move to the validateRegistrationInput()
function and write the logic.
fun validateRegistrationInput(
userName: String,
password: String,
confirmPassword: String
): Boolean {
if (userName.isEmpty() || password.isEmpty()) {
return false
}
if (userName in existingUsers) {
return false
}
if (password != confirmPassword) {
return false
}
if (password.count { it.isDigit() } < 2) {
return false
}
return true
}
I think the above function is self explanatory as it is satisfying all the test-cases we have written so far. Now, go to the test class again and press the play button. All the test cases will be passed now.
Your final RegistrationUtilTest
class will look like this
package com.clintpauldev.unittestingsample
import com.google.common.truth.Truth.assertThat
import org.junit.Test
class RegistrationUtilTest {
@Test
fun `empty username returns false`() {
val result = RegistrationUtil.validateRegistrationInput(
userName = "",
password = "123",
confirmPassword = "123"
)
assertThat(result).isFalse()
}
@Test
fun `valid username and correctly repeated password returns true`() {
val result = RegistrationUtil.validateRegistrationInput(
"clint",
"123",
"123"
)
assertThat(result).isTrue()
}
@Test
fun `username already exists returns false`() {
val result = RegistrationUtil.validateRegistrationInput(
"Peter",
"123",
"123"
)
assertThat(result).isFalse()
}
@Test
fun `empty password returns false`() {
val result = RegistrationUtil.validateRegistrationInput(
"Peter",
"",
""
)
assertThat(result).isFalse()
}
@Test
fun `password repeated incorrectly returns false`() {
val result = RegistrationUtil.validateRegistrationInput(
"Peter",
"123",
"1234"
)
assertThat(result).isFalse()
}
@Test
fun `password contains less than 2 digits returns false`() {
val result = RegistrationUtil.validateRegistrationInput(
"Peter",
"1abcd",
"1abcd"
)
assertThat(result).isFalse()
}
}
Watching all your test cases showing green tick is an amazing feeling. I hope you were able to get the correct results as well. If you faced any problems, don’t worry, I have shared the completed project here. I have learned more things about testing after watching this tutorial by Philipp Lackner. If you are interested in learning more about testing do watch it.
I hope you were able to learn the basics of unit testing and a brief idea about TDD and testing in general. Happy coding. Stay safe.
:)
Article was originally posted at clintpauldev.com.
You may also want to read my recent articles on