JUnit – Parameterized Tests

In this JUnit tutorial, we’ll explore the basics of parameterized tests, their setup, and how to use different sources to supply test data.


What are Parameterized Tests?

Parameterized tests allow a single test method to execute multiple times with different sets of parameters. This eliminates repetitive test cases and makes tests more concise and maintainable.

  • Each test run is executed with a unique combination of input values.
  • Results are evaluated for every set of inputs.
  • JUnit 5 provides built-in annotations and methods to define parameterized tests.

Setting Up Parameterized Tests

To use parameterized tests, ensure your project is configured with JUnit 5 (JUnit Jupiter). Add the following dependency to your pom.xml:

</>
Copy
<dependency>
  <groupId>org.junit.jupiter</groupId>
  <artifactId>junit-jupiter-params</artifactId>
  <version>5.9.2</version>
  <scope>test</scope>
</dependency>

Once this dependency is added, you can begin writing parameterized tests.


Annotations for Parameterized Tests

JUnit 5 provides several annotations to supply parameters to tests:

  • @ParameterizedTest: Marks a test as parameterized.
  • @ValueSource: Supplies a single array of values to the test.
  • @CsvSource: Supplies multiple sets of values separated by commas.
  • @CsvFileSource: Reads test data from a CSV file.
  • @MethodSource: Supplies test data from a static method.

Writing a Parameterized Test

1 Using @ValueSource

The @ValueSource annotation provides a single array of primitive or String values to a parameterized test. Here’s an example:

src/test/java/ValueSourceTest.java

</>
Copy
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class ValueSourceTest {

    @ParameterizedTest
    @ValueSource(ints = {1, 3, 9, 171, 15})
    void isOdd_ShouldReturnTrueForOddNumbers(int number) {
        assertTrue(number % 2 != 0, "Number should be odd");
    }
}

In this test, the method is executed five times, each with a different value from the ints array as shown in the following video.

2 Using @CsvSource

The @CsvSource annotation provides multiple sets of data in CSV format. This is useful for testing methods with multiple arguments.

src/test/java/CsvSourceTest.java

</>
Copy
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class CsvSourceTest {

    @ParameterizedTest
    @CsvSource({
        "2, 3, 5",
        "1, 1, 2",
        "4, 5, 9"
    })
    void testAddition(int a, int b, int expected) {
        assertEquals(expected, a + b, () -> a + " + " + b + " should equal " + expected);
    }
}

Here, the test runs three times, each with a different set of values from the @CsvSource as shown in the following video.

3 Using @CsvFileSource

Use @CsvFileSource to load test data from an external CSV file. This is helpful for managing large data sets.

Steps to Add the CSV File:

  1. Navigate to the src/test/resources directory in your project.
  2. Create a file named test-data.csv.
  3. Add the necessary data to the file, for example.

src/test/resources/test-data.csv

</>
Copy
a,b,expected
2,3,5
4,5,9
1,1,2

src/test/java/CsvFileSourceTest.java

</>
Copy
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvFileSource;

class CsvFileSourceTest {

    @ParameterizedTest
    @CsvFileSource(resources = "/test-data.csv", numLinesToSkip = 1)
    void testFromCsvFile(int a, int b, int expected) {
        assertEquals(expected, a + b);
    }
}

The resources attribute specifies the file path, and numLinesToSkip=1 skips the header row.

When you run this program, the test runs three times, each with a different set of values from the @CsvFileSource as shown in the following video.

4 Using @MethodSource

The @MethodSource annotation allows you to supply parameters from a static method:

src/test/java/MethodSourceTest.java

</>
Copy
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;

class MethodSourceTest {

    static Stream<Arguments> provideAdditionData() {
        return Stream.of(
            Arguments.of(2, 3, 5),
            Arguments.of(1, 1, 2),
            Arguments.of(4, 5, 9)
        );
    }

    @ParameterizedTest
    @MethodSource("provideAdditionData")
    void testWithMethodSource(int a, int b, int expected) {
        assertEquals(expected, a + b);
    }
}

The method provideAdditionData supplies data as a stream of arguments for the test.

When you run this program, the test runs three times, each with a different set of values from the @MethodSource as shown in the following video.


Benefits of Parameterized Tests

  • Efficiency: Reduce repetitive code by testing multiple scenarios in a single test.
  • Readability: Test cases are cleaner and easier to understand.
  • Scalability: Add more test data without modifying test logic.

Best Practices

  • Ensure test data covers all edge cases.
  • Use meaningful names for test methods and data providers.
  • Organize large datasets using @CsvFileSource or external files.
  • Keep tests focused on specific behaviors for clarity.

Conclusion

Parameterized tests are a powerful feature of JUnit that simplify testing multiple inputs and scenarios. By using annotations like @ValueSource, @CsvSource, and @MethodSource, you can create concise, efficient, and maintainable tests that improve the overall quality of your codebase.