Junit 5 parameterized test with Class @ValueSource

This is a follow up to a previous blog post I wrote about Junit 5 parameterized tests. The @ValueSource annotation allows you to specify an array of literal values where a single value is used per invocation of the test. @ValueSource handles all the primitives, String and Class object. The Junit 5 user guide does not have an example of how to use a java.lang.Class @ValueSource.

I have used it to test exception handling. For example, in the following test, it verifies that both JPA and Spring DAO exception for no matching database row are mapped to the OrderNotFoundException. (The exception is annotated with Spring web’s @ResponseStatus to map to the HTTP status 404 NOT FOUND).

@ParameterizedTest
@ValueSource(classes = {NoResultException.class, EmptyResultDataAccessException.class})
  public void testOrderNotFound(Class<? extends Exception> clazz) throws Exception {
    when(orderDao.getOrder(ORDER_ID)).thenThrow(clazz);
    assertThrows(OrderNotFoundException.class,
        () -> provisionService.addNumbers(ORDER_ID, USER));
}


Junit 5 parameterized tests

My favourite feature of Junit 5 is its improvement to parameterized tests. Previously, each parameterized test must be written in its own class. In the test class, you define a method for the test and another method for its inputs and outputs. Junit 5 provides a much simpler way to define parameterized tests. You can now annotate individual test methods as parameterized, with parameters supplied via annotations.

For example, the following test verifies the method under test throws an InvalidRequestException with the specified list of ISO dates.

@ParameterizedTest
@ValueSource(strings= {"1752-12-31T21:45:00", "10000-01-01T21:45:00"})
public void invalidDate(String testdate) {
    testRequest.setDate(testdate);
    assertThrows(InvalidRequestException.class, 
        () -> testService.testMethod(ORDER_ID, testRequest));
}

With the enum value source and exclude mode, it’s now very easy to test status validation to make sure all statuses except a few are allowed. For example, the code below checks an order can be cancelled only in the PREPROCESS status.

@ParameterizedTest
@EnumSource(value = OrderStatus.class, mode = Mode.EXCLUDE, 
            names = {"PREPROCESS"})
public void testOrderStatusCancellation(OrderStatus status) throws Exception {
    testOrder.setStatus(status);
    assertThrows(InvalidRequestException.class
        () -> testService.cancel(testOrder));
}

If your parameterized test inputs and outputs are more complicated, and cannot be easily supplied inside annotation, you can use the @MethodSource annotation. This allows you to define your test method inputs and outputs with another method, similar to how things were before Junit 5.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

@ParameterizedTest
@MethodSource("inputOutputProvider")
public void testCodeParsing(String input, Set<Integer> expected) {
    assertEquals(expected, codeReader.getRejectionCodes(comment));
}

private static Stream<Arguments> inputOutputProvider() {
    return Stream.of(
        Arguments.arguments("0010,0015,0041", newHashSet(10,15,41)),
        Arguments.arguments("C22", newHashSet(22)),
        Arguments.arguments("10", newHashSet(10)));
}