A Smarter Way to Test Your Code with Parameterized Unit Tests — JUnit4, Android
Parameterized Unit Tests are an efficient way of testing your code. Rather than writing individual tests for the same scenarios over and over again, you can create a single test that gets called repeatedly with different inputs and assesses their behavior. This saves time and keeps your tests focused on their objectives.
In this article, we will demonstrate how to implement Parameterized Unit Tests in Kotlin using JUnit4 and Android Studio. The library we’ll use is only JUnit4, but the same concepts can be applied along with other famous libraries such as Mockito, Mockk, or Truth.
Let’s say you’re working on a super cool app that has a Login, Register, and Home page. The registration page is what we’re going to focus on today. When a user signs up, they need to enter an email and password. Of course, the email and password have to pass a validation check. For example, the email has to at least look like a real one and the password has to have at least one letter, one number, and be at least 8 characters long.
As you can see in the video, the code for the registration page is all set and ready to go. But before we can let our users sign up, we need to make sure everything works perfectly. That’s where the ValidateEmail and ValidatePassword use-cases come in. These two use-cases are responsible for making sure the email and password are valid by matching the regex patterns.
ValidateEmail.kt
class ValidateEmail {
operator fun invoke(email: String): Boolean {
return Pattern.compile(
"[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
"\\@" +
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
"(" +
"\\." +
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
")+"
).matcher(email)
.matches()
}
}
ValidatePassword.kt
class ValidatePassword {
operator fun invoke(password: String): Boolean {
return Pattern.compile("^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}\$").matcher(password)
.matches()
}
}
So, how do we test these functions to make sure they’re doing their job? That’s where Parameterized Unit Tests come in! With these tests, you can run the same test multiple times with different inputs and see how the code reacts.
Let’s start by testing the ValidateEmail use-case class. We’ll create a test file called ValidateEmailTest.kt and get to work. To write a parameterized test, we need to annotate the class with @RunWith(Parameterized::class) and create a public constructor that takes in parameters for each test run. In our case, we’ll have two parameters — the email (input) and isValid (expected output).
@RunWith(Parameterized::class)
class ValidateEmailTest(
private val email: String,
private val isValid: Boolean
) {}
Let’s add and initialze the class that we are testing here:
lateinit var validateEmail: ValidateEmail
@Before
fun setUp() {
validateEmail = ValidateEmail()
}
We’ll provide 20 different combinations of email and isValid in a companion object annotated with @Parameterized.Parameters. This means our test will run 20 times with different parameters and we’ll see how our code behaves with different inputs.
companion object {
@JvmStatic
@Parameterized.Parameters(
name = "when email is {0}, then we are getting {1}"
)
fun getValidateEmailResult(): Iterable<Array<Any>> {
/* (email, isValid) */
return arrayListOf(
arrayOf("abc", false),
arrayOf("abc@abc", false),
arrayOf("abc@abc.com", true),
arrayOf("kamran@gmail.pk", true),
arrayOf("TheDidacticGamer@gmail.com", true),
arrayOf("123456", false),
arrayOf("TheDetailedGamer@gmail.com", true),
arrayOf("username", false),
arrayOf(".,uflu", false),
arrayOf("TheHarmoniousGamer@gmail.com", true),
arrayOf("@.veryshortname", false),
arrayOf("yzr", false),
arrayOf("TheBlueGamer@gmail.com", true),
arrayOf("TheAmusedGamer@gmail.com", true),
arrayOf("@abc@.com", false),
arrayOf("randomemail.com", false),
arrayOf("TheFascinatedGamer@gmail.com", true),
arrayOf("https://google.com", false),
arrayOf("niceemaildear", false),
arrayOf("TheJudiciousGamer@gmail.co", true),
)
}
}
Finally, we’ll write the actual test. This test will just call the ValidateEmail use-case with our different inputs and compare the outputs to what we expect.
@Test
fun `test validate email`() {
val result = validateEmail(email)
Assert.assertEquals(result, isValid)
}
And just like that, our ValidateEmailTest.kt is done and ready to go! Running this test will give us 20 different results with just one run. Ain’t that cool?
Now lets try changing arrayOf(“abc”, false) to arrayOf(“abc”, true) to see it fail and confirm that every test passing not just a fluke.
As you can see when the email is abc, the validateEmail use case is returning false as it is not a valid email hence our wrong output test failed. So let’s revert it back to false, everything is working as expected.
Now, it’s time to test the ValidatePassword function. We’ll create another test file called ValidatePasswordTest.kt and repeat the process.
The complete test files now look like this:
ValidateEmailTest.kt
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
class ValidateEmailTest(
private val email: String,
private val isValid: Boolean
) {
lateinit var validateEmail: ValidateEmail
@Before
fun setUp() {
validateEmail = ValidateEmail()
}
companion object {
@JvmStatic
@Parameterized.Parameters(
name = "when email is {0}, then we are getting {1}"
)
fun getValidateEmailResult(): Iterable<Array<Any>> {
/* (email, isValid) */
return arrayListOf(
arrayOf("abc", false),
arrayOf("abc@abc", false),
arrayOf("abc@abc.com", true),
arrayOf("kamran@gmail.pk", true),
arrayOf("TheDidacticGamer@gmail.com", true),
arrayOf("123456", false),
arrayOf("TheDetailedGamer@gmail.com", true),
arrayOf("username", false),
arrayOf(".,uflu", false),
arrayOf("TheHarmoniousGamer@gmail.com", true),
arrayOf("@.veryshortname", false),
arrayOf("yzr", false),
arrayOf("TheBlueGamer@gmail.com", true),
arrayOf("TheAmusedGamer@gmail.com", true),
arrayOf("@abc@.com", false),
arrayOf("randomemail.com", false),
arrayOf("TheFascinatedGamer@gmail.com", true),
arrayOf("https://google.com", false),
arrayOf("niceemaildear", false),
arrayOf("TheJudiciousGamer@gmail.co", true),
)
}
}
@Test
fun `test validate email`() {
val result = validateEmail(email)
Assert.assertEquals(result, isValid)
}
}
ValidatePasswordTest.kt
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
@RunWith(Parameterized::class)
class ValidatePasswordTest(
private val password: String,
private val isValid: Boolean
) {
lateinit var validatePassword: ValidatePassword
@Before
fun setUp() {
validatePassword = ValidatePassword()
}
companion object {
@JvmStatic
@Parameterized.Parameters(
name = "when password is {0}, then we are getting {1}"
)
fun getValidateEmailResult(): Iterable<Array<Any>> {
/* (password, isValid) */
return arrayListOf(
arrayOf("abcdefg", false),
arrayOf("12345678", false),
arrayOf("Abcd1234", true),
arrayOf("Kamrean1234", true),
arrayOf("abcD1234", true),
arrayOf("12345Ab", false),
arrayOf("QO0934556", true),
arrayOf("password", false),
arrayOf("./,'./,;./,;.,", false),
arrayOf("TheGreatAbcd1", true),
arrayOf("@.wierdpassword", false),
arrayOf("yzr", false)
)
}
}
@Test
fun `test validate password`() {
val result = validatePassword(password = password)
assertEquals(result, isValid)
}
}
The Complete code is available HERE.