JUnit Parameterized Tests

Introduction

In this chapter, we will explore JUnit parameterized tests. Parameterized tests allow you to run the same test with different inputs. This is useful for testing a method with a variety of data sets without having to write multiple test methods.

What are Parameterized Tests?

Parameterized tests are a way to execute the same test method with different parameters. This is especially useful for testing methods that should behave consistently across a range of inputs. JUnit 5 provides excellent support for parameterized tests through the @ParameterizedTest annotation and a variety of sources for parameters.

Overview of Parameterized Test Annotations

JUnit 5 provides several annotations and methods to create parameterized tests. Here is an overview of the main annotations:

@ParameterizedTest

The @ParameterizedTest annotation is used to mark a method as a parameterized test. This annotation tells JUnit to run the test method multiple times with different parameters.

@ValueSource

The @ValueSource annotation provides a simple source of literal values. It can be used to specify arrays of ints, longs, doubles, strings, and other primitive types. This is useful for testing a method with a fixed set of simple values.

Example:

@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void testWithIntValueSource(int argument) {
    assertTrue(argument > 0);
}

@CsvSource

The @CsvSource annotation provides a source of comma-separated values. It is useful for testing methods with multiple parameters. Each line in the @CsvSource represents a set of parameters to be passed to the test method.

Example:

@ParameterizedTest
@CsvSource({
    "1, 1, 2",
    "2, 3, 5",
    "3, 5, 8"
})
void testWithCsvSource(int a, int b, int expectedSum) {
    assertEquals(expectedSum, a + b);
}

@MethodSource

The @MethodSource annotation provides a source of arguments from a static method in the same class or another class. The method must return a stream, iterable, iterator, or array of arguments.

Example:

@ParameterizedTest
@MethodSource("provideStringsForIsBlank")
void testWithMethodSource(String input, boolean expected) {
    assertEquals(expected, isBlank(input));
}

private static Stream<Arguments> provideStringsForIsBlank() {
    return Stream.of(
        Arguments.of(null, true),
        Arguments.of("", true),
        Arguments.of("  ", true),
        Arguments.of("not blank", false)
    );
}

@EnumSource

The @EnumSource annotation provides a source of enum constants. It can be used to test methods with different enum values.

Example:

@ParameterizedTest
@EnumSource(TimeUnit.class)
void testWithEnumSource(TimeUnit timeUnit) {
    assertNotNull(timeUnit);
}

@ArgumentsSource

The @ArgumentsSource annotation allows for custom argument sources by implementing the ArgumentsProvider interface. This provides maximum flexibility in defining argument sources.

Example:

@ParameterizedTest
@ArgumentsSource(MyArgumentsProvider.class)
void testWithArgumentsSource(String argument) {
    assertNotNull(argument);
}

static class MyArgumentsProvider implements ArgumentsProvider {
    @Override
    public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
        return Stream.of("one", "two", "three").map(Arguments::of);
    }
}

Example: Creating Parameterized Tests

Maven Dependencies

To use parameterized tests in JUnit 5, you need to include the following dependencies in your pom.xml file:

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.10.3</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.10.3</version>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-params</artifactId>
        <version>5.10.3</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Explanation of Dependencies

  • JUnit Jupiter API (junit-jupiter-api): This dependency provides the JUnit 5 annotations and assertions.
  • JUnit Jupiter Engine (junit-jupiter-engine): This dependency is the runtime engine that executes JUnit 5 tests.
  • JUnit Jupiter Params (junit-jupiter-params): This dependency provides support for parameterized tests in JUnit 5.

Step 1: Create Class Under Test

Let’s create parameterized tests for a simple utility class called MathUtils.

MathUtils Class

The MathUtils class will have methods for checking if a number is even and for calculating the factorial of a number.

public class MathUtils {

    public boolean isEven(int number) {
        return number % 2 == 0;
    }

    public long factorial(int number) {
        if (number < 0) throw new IllegalArgumentException("Number must be non-negative");
        long result = 1;
        for (int i = 1; i <= number; i++) {
            result *= i;
        }
        return result;
    }
}

Step 2: Create a Parameterized Test Class

MathUtilsTest

The MathUtilsTest class will contain parameterized tests for the MathUtils class.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.*;

import java.util.stream.Stream;

public class MathUtilsTest {

    private MathUtils mathUtils = new MathUtils();

    @ParameterizedTest
    @ValueSource(ints = {2, 4, 6, 8, 10})
    public void testIsEvenWithEvenNumbers(int number) {
        assertTrue(mathUtils.isEven(number), number + " should be even");
    }

    @ParameterizedTest
    @ValueSource(ints = {1, 3, 5, 7, 9})
    public void testIsEvenWithOddNumbers(int number) {
        assertFalse(mathUtils.isEven(number), number + " should be odd");
    }

    @ParameterizedTest
    @CsvSource({"0, 1", "1, 1", "2, 2", "3, 6", "4, 24", "5, 120"})
    public void testFactorialWithCsvSource(int input, long expected) {
        assertEquals(expected, mathUtils.factorial(input), "Factorial of " + input + " should be " + expected);
    }

    @ParameterizedTest
    @MethodSource("provideNumbersForIsEven")
    public void testIsEvenWithMethodSource(int number, boolean expected) {
        assertEquals(expected, mathUtils.isEven(number), number + " should be " + (expected ? "even" : "odd"));
    }

    private static Stream<org.junit.jupiter.params.provider.Arguments> provideNumbersForIsEven() {
        return Stream.of(
            org.junit.jupiter.params.provider.Arguments.of(2, true),
            org.junit.jupiter.params.provider.Arguments.of(3, false),
            org.junit.jupiter.params.provider.Arguments.of(4, true),
            org.junit.jupiter.params.provider.Arguments.of(5, false)
        );
    }
}

Explanation of the Parameterized Test Class

  • @ParameterizedTest: This annotation marks the method as a parameterized test.
  • @ValueSource: This annotation provides a simple source of literal values. It can be used to specify arrays of ints, strings, etc.
  • @CsvSource: This annotation provides a source of comma-separated values. It can be used to specify multiple sets of parameters for the test method.
  • @MethodSource: This annotation provides a source of arguments from a static method. The method must return a stream of arguments.

Running the Parameterized Tests

To run the parameterized tests, simply run the MathUtilsTest class as a JUnit test. This will execute the test methods with each set of parameters provided by the @ValueSource, @CsvSource, and @MethodSource annotations.

Using IntelliJ IDEA

  1. Run Parameterized Tests: Click the green run icon next to the MathUtilsTest class and select Run.
  2. View Results: The results will be displayed in the Run window. A green check mark indicates that all tests passed, while a red cross indicates that some tests failed.

JUnit Parameterized Tests

Conclusion

JUnit parameterized tests are a powerful way to test your code with multiple sets of input data. By using annotations like @ParameterizedTest, @ValueSource, @CsvSource, and @MethodSource, you can create flexible and comprehensive tests that ensure your methods behave correctly across a range of inputs. This approach helps you write more robust and maintainable tests for your Java applications.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top