Writing Scalable Parameterized Tests in JUnit 5
- Published on
Writing Scalable Parameterized Tests in JUnit 5
When it comes to testing in Java, JUnit is a widely-used framework due to its simplicity and effectiveness. In this blog post, we'll explore how to write scalable parameterized tests in JUnit 5. Parameterized tests allow us to run the same test with different inputs, which is essential when dealing with repetitive test cases.
What are parameterized tests?
In traditional unit tests, you write a test method for each individual case you want to test. But what if you have a set of similar test cases that only differ in their input values? This is where parameterized tests come in handy. They allow you to write a single test method and run it with different input values.
Setting up the project
Before we dive into writing parameterized tests, let's set up a simple Java project with JUnit 5.
// pom.xml
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
</dependencies>
Once you have the JUnit 5 dependency in your project, you're ready to start writing parameterized tests.
Writing parameterized tests in JUnit 5
In JUnit 5, parameterized tests are defined using the @ParameterizedTest
annotation. Let's say we want to test a simple Calculator
class that has a method to add two numbers. We'll write a parameterized test to test the add
method with multiple inputs.
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
// CalculatorTest.java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CalculatorTest {
private Calculator calculator = new Calculator();
@ParameterizedTest
@CsvSource({"1, 1, 2", "2, 3, 5", "5, 5, 10"})
void testAdd(int a, int b, int expected) {
assertEquals(expected, calculator.add(a, b));
}
}
In the above example, we use the @CsvSource
annotation to define the input values for the test method. Each row in the CSV represents a set of input values, followed by the expected result. The test method is then annotated with @ParameterizedTest
and takes parameters corresponding to the input values.
Using method source for parameterized tests
Instead of using @CsvSource
, you can also use a method that returns a Stream of arguments for parameterized tests. This is useful when you have a large set of input values or when you need to generate them dynamically.
// CalculatorTest.java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CalculatorTest {
private Calculator calculator = new Calculator();
static Stream<Arguments> addProvider() {
return Stream.of(
Arguments.of(1, 1, 2),
Arguments.of(2, 3, 5),
Arguments.of(5, 5, 10)
);
}
@ParameterizedTest
@MethodSource("addProvider")
void testAdd(int a, int b, int expected) {
assertEquals(expected, calculator.add(a, b));
}
}
In the above example, the addProvider
method returns a Stream
of Arguments
, each representing a set of input values. The @MethodSource
annotation is then used to specify the method as the source for the test arguments.
Custom argument providers
Sometimes, you may need to generate test arguments dynamically or from a different source. In such cases, you can create custom argument providers by implementing the ArgumentsProvider
interface.
// RandomNumbersProvider.java
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.ArgumentsProvider;
import java.util.stream.Stream;
public class RandomNumbersProvider implements ArgumentsProvider {
@Override
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return Stream.of(
Arguments.of(3, 4, 7),
Arguments.of(10, 20, 30),
Arguments.of(5, 5, 10)
);
}
}
// CalculatorTest.java
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ArgumentsSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CalculatorTest {
private Calculator calculator = new Calculator();
@ParameterizedTest
@ArgumentsSource(RandomNumbersProvider.class)
void testAdd(int a, int b, int expected) {
assertEquals(expected, calculator.add(a, b));
}
}
In this example, RandomNumbersProvider
implements the ArgumentsProvider
interface and provides the test arguments dynamically. The @ArgumentsSource
annotation is then used to specify the custom argument provider for the parameterized test.
Closing the Chapter
In this blog post, we've explored how to write scalable parameterized tests in JUnit 5. We've covered using @CsvSource
and @MethodSource
for providing test arguments, as well as creating custom argument providers. Parameterized tests are an essential tool in any test suite, allowing you to write concise and maintainable tests for repetitive scenarios.
By leveraging the flexibility and reusability of parameterized tests in JUnit 5, you can streamline your testing process, making it more efficient and effective. With this knowledge, you can write tests that scale with your application, ensuring its reliability and robustness.
Start incorporating parameterized tests in your JUnit 5 test suite and experience the benefits of writing cleaner, more expressive tests with less repetitive code. Happy testing!
Remember, the most effective tests are those that provide meaningful coverage of your code and help you catch potential bugs before they become issues in the production environment. Happy testing!