12

I need something similar to C# NUnit TestCase scenario with fixed expected results.

I've already found this question on SO which has great solutions but they are for Java only and as suggested in some of the answers I followed this tutorial, but simply converting it to Kotlin doesn't work.

@RunWith(value = Parameterized.class)
public class EmailIdValidatorTest {

    private String emailId;
    private boolean expected;

    public EmailIdValidatorTest(String emailId, boolean expected) {
        this.emailId = emailId;
        this.expected = expected;
    }
    @Parameterized.Parameters(name= "{index}: isValid({0})={1}")
    public static Iterable<Object[]> data() {
        return Arrays.asList(new Object[][]{
                        {"mary@testdomain.com", true},
                        {"mary.smith@testdomain.com", true},
                        {"mary_smith123@testdomain.com", true},
                        {"mary@testdomaindotcom", false},
                        {"mary-smith@testdomain", false},
                        {"testdomain.com", false}
                }
        );
    }
    @Test
    public void testIsValidEmailId() throws Exception {
        boolean actual= EmailIdUtility.isValid(emailId);
        assertThat(actual, is(equalTo(expected)));
    }
}

So what is the correct way to write Kotlin parametrized unit tests with JUnit?

MatPag
  • 41,742
  • 14
  • 105
  • 114

2 Answers2

13

Following this amazing tutorial, we can implement it in Kotlin language in this way:

First of all convert the EmailIdUtility class into Kotlin:

object EmailIdUtility {
    fun isValid(email: String): Boolean {
        val regex =
            "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$"
        val pattern = Pattern.compile(regex)
        val m = pattern.matcher(email)
        return m.matches()
    }
}

Then convert the EmailIdValidatorTest into Kotlin

@RunWith(value = Parameterized::class)
class EmailIdValidatorTest(
    private val email: String,
    private val expected: Boolean)
{
    @Test
    fun testIsValidEmailId() {
        val actual = EmailIdUtility.isValid(email)
        assertEquals(expected, actual)
    }

    companion object {
        @JvmStatic
        @Parameterized.Parameters(name = "{index}: isValid({0})={1}")
        fun data(): Iterable<Array<Any>> {
            return arrayListOf(
                arrayOf("mary@testdomain.com", true),
                arrayOf("mary.smith@testdomain.com", true),
                arrayOf("mary_smith123@testdomain.com", true),
                arrayOf("mary@testdomaindotcom", false),
                arrayOf("mary-smith@testdomain", false),
                arrayOf("testdomain.com", false)
            ).toList()
        }
    }
}

Remember to add @JvmStatic in the data() method, otherwise you will get the error: java.lang.Exception: No public static parameters method on class com.example.testapp.dev.EmailIdValidatorTest


EASIER WAY

If you can use another library (working in Android too) I suggest you to add JUnitParams to your test dependencies, in Android it could be:

testImplementation  "pl.pragmatists:JUnitParams:1.1.1"

Then you could convert the above class in this way:

@RunWith(JUnitParamsRunner::class)
class EmailIdValidatorTest {

    @Test
    @Parameters(value = [
        "mary@testdomain.com, true",
        "mary.smith@testdomain.com, true",
        "mary_smith123@testdomain.com, true",
        "mary@testdomaindotcom, false",
        "mary-smith@testdomain, false",
        "testdomain.com, false"
    ])
    fun testIsValidEmailId(email: String, expected: Boolean) {
        val actual = EmailIdUtility.isValid(email)
        assertEquals(expected, actual)
    }
}

which for me is a lot easier than the JUnit way.

For more examples on how to use JUnitParams you can check the link.

With JUnit 5 this is a lot easier but currently JUnit 5 is not supported for Android tests if you don't use Android-JUnit 5 too.

MatPag
  • 41,742
  • 14
  • 105
  • 114
  • @ MatPag this is really nice explanation. can you also suggest how we can create multiple test methods in the same class to run against parameterized data. – Shah Oct 01 '20 at 23:23
8

I know you're using JUnit 4, but if you consider to update to JUnit 5, you can use @TestFactory annotation to execute parametrized unit test.

In your case that would result in a test class like this:

import org.junit.jupiter.api.DynamicTest
import org.junit.jupiter.api.TestFactory

internal class EmailIdValidatorTest {

    @TestFactory
    fun `test email id validity`() =
        listOf(
            "mary@testdomain.com" to true,
            "mary.smith@testdomain.com" to true,
            "mary_smith123@testdomain.com" to true,
            "mary@testdomaindotcom" to false,
            "mary-smith@testdomain" to false,
            "testdomain.com" to false
        ).map {
            dynamicTest("email ${it.first} should be ${if (it.second) "valid" else "not valid" }") {
                val actual = EmailIdUtility.isValid(it.first)
                assertEquals(expected, actual)
            }
        }

}
noiaverbale
  • 1,550
  • 13
  • 27
  • This is a nice addition, thank you (even if currently JUnit 5 is not directly supported on Android) – MatPag Mar 20 '19 at 11:49
  • This is different from the JUnit way in the sense it either passes or fails on one of the assertions, while JUnit parameterized runs parameters independently – Pedro Borges May 16 '22 at 22:35