Author

Author- Ram Ranjeet Kumar

Sunday, August 6, 2023

@CsvSource Annotation With @ParameterizedTest in JUnit5





  • The @CsvSource annotation is a way to provide comma-separated values (CSV) as arguments for a parameterized test method in JUnit 5. 
  • It allows you to specify one or more CSV records that will be used as the source of arguments for each invocation of the test method. 
  • You can customize the column delimiter, the quote character, the empty value, and the null values using the annotation attributes. 
  • You can also use a text block to write multiple CSV records in a more readable way.
  • You can use various types of arguments and datatypes, such as primitives, enums, arrays, collections, and custom types. 
  • However, you need to make sure that the arguments can be converted to the target parameter types by using implicit or explicit converters.


Here is an example of how to use the @CsvSource annotation:

import org.junit.jupiter.params.ParameterizedTest;

import org.junit.jupiter.params.provider.CsvSource;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    @ParameterizedTest

    @CsvSource({

        "0,    1,   1",

        "1,    2,   3",

        "49,  51, 100",

        "1,  100, 101"

    })

    void add(int first, int second, int expectedResult) {

        Calculator calculator = new Calculator();

        assertEquals(expectedResult, calculator.add(first, second),

                () -> first + " + " + second + " should equal " + expectedResult);

    }

}


  • This test method will be executed four times with different values for the first, second, and expectedResult parameters. 
  • The values are taken from the CSV records provided by the @CsvSource annotation. 
  • The column delimiter is a comma (,) by default, but you can change it using the delimiter or delimiterString attributes.


Here are some examples of how to use the @CsvSource annotation with different types of arguments and datatypes:


Primitives: You can use primitive types such as int, long, double, boolean, etc. as arguments for the test method. For example:

@ParameterizedTest

@CsvSource({"1, 2, 3", "4, 5, 9", "7, 8, 15"})

void testAdd(int a, int b, int expected) {

    assertEquals(expected, a + b);

}


Enums: You can use enum constants as arguments for the test method. For example:

enum Color {

    RED, GREEN, BLUE

}

@ParameterizedTest

@CsvSource({"RED, 255, 0, 0", "GREEN, 0, 255, 0", "BLUE, 0, 0, 255"})

void testColor(Color color, int r, int g, int b) {

    assertEquals(r, color.getRed());

    assertEquals(g, color.getGreen());

    assertEquals(b, color.getBlue());

}


Arrays: You can use arrays as arguments for the test method by using curly braces ({}) to enclose the array elements. For example:

@ParameterizedTest

@CsvSource({"1, {2, 3}", "4, {5, 6}", "7, {8}"})

void testArray(int a, int[] b) {

    assertTrue(a > 0);

    assertTrue(b.length > 0);

}


Collections: You can use collections such as List or Set as arguments for the test method by using curly braces ({}) to enclose the collection elements. For example:


@ParameterizedTest

@CsvSource({"1, {2, 3}", "4, {5}", "7"})

void testCollection(int a, List<Integer> b) {

    assertTrue(a > 0);

    assertNotNull(b);

}


Custom types: You can use custom types as arguments for the test method by implementing an ArgumentConverter that can convert a String to the custom type. For example:


class Person {

    private String name;

    private int age;

    // constructor and getters omitted for brevity

}


class PersonConverter implements ArgumentConverter {

    @Override

    public Object convert(Object source, ParameterContext context) throws ArgumentConversionException {

        if (!(source instanceof String)) {

            throw new IllegalArgumentException("The argument should be a string: " + source);

        }

        String[] parts = ((String) source).split(":");

        if (parts.length != 2) {

            throw new IllegalArgumentException("The argument should have two parts separated by a colon: " + source);

        }

        String name = parts[0];

        int age = Integer.parseInt(parts[1]);

        return new Person(name, age);

    }

}


@ParameterizedTest

@CsvSource({"Alice:23", "Bob:42", "Charlie:28"})

void testPerson(@ConvertWith(PersonConverter.class) Person person) {

    assertNotNull(person.getName());

    assertTrue(person.getAge() > 0);

}


You can use a different delimiter character or string for the CSV records, such as a semicolon (;) or a pipe (|). For example:

@ParameterizedTest

@CsvSource (delimiter = ';', value = {

    "apple; 1",

    "banana; 2",

    "'lemon; lime'; 3"

})

void testWithSemicolonDelimiter(String fruit, int rank) {

    assertNotNull(fruit);

    assertNotEquals(0, rank);

}


You can use a different quote character for the CSV records, such as a double quote (\"). For example:

@ParameterizedTest

@CsvSource (quoteCharacter = '"', value = {

    "\"apple, banana\"; 1",

    "\"lemon, lime\"; 2"

})

void testWithDoubleQuote(String fruits, int rank) {

    assertTrue(fruits.contains(","));

    assertNotEquals(0, rank);

}


You can use a text block to write multiple CSV records in a more readable way. For example:

@ParameterizedTest

@CsvSource (textBlock = """

    name, age

    Alice, 23

    Bob, 42

    Charlie, 28

""")

void testWithTextBlock(String name, int age) {

    assertNotNull(name);

    assertTrue(age > 0);

}


You can use the nullValues attribute to specify one or more strings that should be interpreted as null references. For example:

@ParameterizedTest

@CsvSource (nullValues = {"NULL", "N/A"}, value = {

    "apple, NULL",

    "banana, N/A",

    "lemon, ''"

})

void testWithNullValues(String fruit, String color) {

    assertNotNull(fruit);

    assertNull(color);

}

You can find more information and examples about the @CsvSource annotation in the [JUnit 5 documentation]. 

No comments:

Post a Comment