Parameterized tests can be very helpful when testing expected results against various input data. Instead of writing several tests you can parameterize your test values are write a single method to verify the results.
Sample Problem
Here’s how a test for the following StringMutator
class might look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // Example class to test public class StringMutator { public static String captilizeString(String input) { return input.toUpperCase(); } } // Example test (simplified for clarity) public class ExampleUnitTest { @Test public void testCapitalizeString() { assertEquals(StringMutator.captilizeString("something"), "SOMETHING"); assertEquals(StringMutator.captilizeString("Something"), "SOMETHING"); assertEquals(StringMutator.captilizeString("W3irdData"), "W3IRDDATA"); assertEquals(StringMutator.captilizeString(""), ""); assertEquals(StringMutator.captilizeString("SS"), "SS"); } } |
Parameterized Testing
While this test (while virtually pointless) is a potential candidate for parameterized testing. The test data can be “parameterized” the above test can be reduced to a single line.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | package com.example.rileymacdonald.parameterizedtest; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.lang.Iterable; import java.util.Arrays; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.junit.runners.Parameterized.Parameters; @RunWith(Parameterized.class) public class ExampleUnitTest { @Parameters public static Iterable<Object[][]> data() { return Arrays.asList(new Object[][]{ {"something", "SOMETHING"}, {"Something", "SOMETHING"}, {"W3irdData", "W3IRDDATA"}, {"", ""}, {"SS", "SS"} }); } private final String input; private final String expectedResult; public ExampleUnitTest(String input, String expectedResult) { this.input = input; this.expectedResult = expectedResult; } @Test public void testCapitalizeString() { assertThat(StringMutator.captilizeString(input), is(equalTo(expectedResult))); } } |
More data can be added to the @Parameters
object. For example your test method might take more than one argument. There’s a bit of setup involved but the tests become more readable / maintainable when the datasets grow.
TNG – JUnit Data Provider Library
I recently wrote some tests using the TNG/junit-dataprovider library. While it adds a test dependency to your module it cuts down the boilerplate involved. Here’s an example of how this same test would look using @DataProvider
:
Add the test dependency to your build.gradle
1 2 | testImplementation 'junit:junit:4.12' testImplementation 'com.tngtech.java:junit-dataprovider:1.11.0' |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; import org.junit.Test; import org.junit.runner.RunWith; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.assertThat; @RunWith(DataProviderRunner.class) public class ExampleUnitTest { // Declare a data provider @DataProvider public static Object[][] provideTestStrings() { return new Object[][]{ {"something", "SOMETHING"}, {"Something", "SOMETHING"}, {"W3irdData", "W3IRDDATA"}, {"", ""}, {"SS", "SS"} }; } // Reference the declared data provider @UseDataProvider("provideTestStrings") @Test public void testCapitalizeString(String input, String expected) { // The method arguments represent the data coming from the above data provider assertThat(StringMutator.captilizeString(input), is(equalTo(expected))); } } |