Java > Testing in Java > Unit Testing > Parameterized Tests
Parameterized Unit Testing with JUnit 5
This snippet demonstrates how to use JUnit 5's parameterized tests to run the same test multiple times with different inputs. This is particularly useful for testing boundary conditions, different data types, and a range of values for a single method or functionality. JUnit 5 offers several ways to provide these parameters, including ValueSource, MethodSource, and CsvSource, making parameterized testing a flexible and powerful tool in your testing arsenal.
Setting up JUnit 5 Dependency
Before you start, make sure you have JUnit 5 dependencies in your project. The snippets above show how to add JUnit 5 to your Maven or Gradle project. Replace `5.10.0` with the latest stable version.
<!-- Maven -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<!-- Gradle -->
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0'
Example: Parameterized Test with ValueSource
This example demonstrates using @ValueSource
to provide different string values to the isPalindrome
method in the StringUtils
class. The @ParameterizedTest
annotation marks the method as a parameterized test. The @ValueSource
annotation specifies an array of strings to be used as input for each test run. The isPalindrome
method checks if a given string is a palindrome, ignoring non-alphanumeric characters and case.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.*;
class StringUtils {
static boolean isPalindrome(String input) {
String cleanInput = input.replaceAll("[^a-zA-Z0-9]", "").toLowerCase();
String reversedInput = new StringBuilder(cleanInput).reverse().toString();
return cleanInput.equals(reversedInput);
}
}
class PalindromeTest {
@ParameterizedTest
@ValueSource(strings = {"racecar", "A man, a plan, a canal: Panama", "madam"})
void isPalindrome_ShouldReturnTrueForPalindromes(String candidate) {
assertTrue(StringUtils.isPalindrome(candidate));
}
@ParameterizedTest
@ValueSource(strings = {"hello", "world", "java"})
void isPalindrome_ShouldReturnFalseForNonPalindromes(String candidate) {
assertFalse(StringUtils.isPalindrome(candidate));
}
}
Example: Parameterized Test with MethodSource
This example demonstrates using @MethodSource
to provide parameters to a parameterized test. The additionExamples
method returns a Stream
of Object
arrays, where each array contains the input values and the expected result for the add
method of the Calculator
class. The @MethodSource
annotation specifies the name of the method that provides the parameters. This allows for more complex parameter generation than @ValueSource
.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
class Calculator {
public int add(int a, int b) {
return a + b;
}
}
class CalculatorTest {
private final Calculator calculator = new Calculator();
@ParameterizedTest
@MethodSource("additionExamples")
void add_ShouldReturnCorrectSum(int a, int b, int expected) {
assertEquals(expected, calculator.add(a, b));
}
static Stream<Object[]> additionExamples() {
return Stream.of(
new Object[]{1, 2, 3},
new Object[]{5, 5, 10},
new Object[]{10, -3, 7}
);
}
}
Example: Parameterized Test with CsvSource
This example demonstrates using @CsvSource
to provide parameters to a parameterized test in a comma-separated value format. Each line in the @CsvSource
annotation represents a set of input values and the expected result. The calculateLength_ShouldReturnCorrectLength
test method takes the input string and the expected length as parameters, and asserts that the calculateLength
method returns the correct length.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.*;
class StringLength {
static int calculateLength(String input) {
return input.length();
}
}
class StringLengthTest {
@ParameterizedTest
@CsvSource({
"hello, 5",
"world, 5",
"java, 4",
"'', 0" // Empty string
})
void calculateLength_ShouldReturnCorrectLength(String input, int expectedLength) {
assertEquals(expectedLength, StringLength.calculateLength(input));
}
}
Concepts Behind Parameterized Testing
Parameterized testing involves running the same test logic multiple times with different sets of input data. This reduces code duplication and makes tests more concise and easier to maintain. JUnit 5's parameterized test feature enables developers to provide different sources for the test parameters, making it a powerful tool for comprehensive testing.
Real-Life Use Case
Consider a validation function that needs to verify if an email address is valid based on several rules. Instead of writing separate test methods for each rule, you can create a parameterized test with a CSV source containing different email addresses and their expected validity. This approach significantly reduces code duplication and makes the test suite more maintainable.
Best Practices
@ValueSource
is suitable for simple values, while @MethodSource
or @CsvSource
are better for more complex data sets.
Interview Tip
Be prepared to explain the benefits of parameterized testing over traditional unit testing, such as reduced code duplication and improved test coverage. Also, be ready to discuss the different types of parameter sources available in JUnit 5 and when to use each one.
When to Use Them
Parameterized tests are beneficial when you need to test the same code with a variety of inputs and expected outputs. They are especially useful for testing boundary conditions, validating input data, and ensuring that code handles different data types correctly.
Memory Footprint
Parameterized tests may have a slightly larger memory footprint than traditional unit tests because they need to store the test data in memory. However, the memory overhead is usually minimal and should not be a concern unless you are dealing with extremely large datasets.
Alternatives
Pros
Cons
@MethodSource
or @CsvSource
.
FAQ
-
What is the main advantage of using Parameterized Tests?
The main advantage is reducing code duplication. Instead of writing multiple similar test methods with different inputs, you can write a single parameterized test that runs with multiple sets of data. -
How do I provide data to a Parameterized Test?
JUnit 5 provides several annotations for supplying data:@ValueSource
for simple values,@MethodSource
for data generated by a method, and@CsvSource
for data in CSV format. -
Can I use Parameterized Tests with Spring Boot?
Yes, you can use Parameterized Tests with Spring Boot. Make sure to include the necessary JUnit 5 and Spring Test dependencies in your project. -
How do I handle null values in Parameterized Tests?
You can use@NullSource
or@NullAndEmptySource
annotations to provide null or empty string values as input to your parameterized tests.