DEV Community

Elias Nogueira
Elias Nogueira

Posted on

JUnit 5 – When to use CSV Providers

Introduction

This article belongs to the Managing Test Data series.
I will show you different ways to smartly use testing framework features when the date changes but the test output is the same.

In this article, you will see one use case for the @CsvSource and @CsvFileSource features. These annotations are particularly useful when you want to provide test data in CSV (Comma-Separated Values) format either directly in the source code or from an external file.

@CsvSource

This Source of Argument in JUnit 5 allows you to use the data as CSV values to allow you either a fast prototype of a CSV file consumption in a test or by the simple use of it to give you the advantage of readability for small data sets. It has different ways to configure it, but let’s focus first on the basic usage.

class CsvSourceTest {
    private static final String MAXIMUM_PRICE = "30.0";

    @DisplayName("Products should not exceed the maximum price")
    @ParameterizedTest(name = "product ''{0}'' of amount ${1} does not exceeds $" + MAXIMUM_PRICE)
    @CsvSource({
            "Micro SD Card 16Gb, 6.09",
            "JBL GO 2, 22.37",
            "iPad Air Case, 14.99",

    })
    void productsLassThan(String product, BigDecimal amount) {
        assertThat(product).isNotEmpty();
        assertThat(amount).isLessThanOrEqualTo(new BigDecimal(MAXIMUM_PRICE));
    }
}
Enter fullscreen mode Exit fullscreen mode

Lines 6 to 11 show you the basic usage of the @CsvSource. Note that:

  • The CSV data is placed between brackets in the annotation
  • Each CSV line has double quotes at the beginning and end of it
  • All the values are separated by a comma, after the end of the double quotes.

The test output will look like this:

CsvSourceTest
└─ Products should not exceed the maximum price
  ├─ [1] product 'Micro SD Card 16Gb' of amount $6.09 does not exceeds $30.0 ✔
  ├─ [2] product 'JBL GO 2' of amount $22.37 does not exceeds $30.0 ✔
  └─ [3] product 'iPad Air Case' of amount $14.99 does not exceeds $30.0 ✔
Enter fullscreen mode Exit fullscreen mode

Annotation configurations

There are different ways to change the @CsvSource behavior:

Annotation configuration What it does
value This is the default.
delimiter Set the delimiter as a char. The default value is a comma (,)
delimiterString Set the delimiter as a String. The default is a comma (,)
emptyValue Set what will be added as an empty value. The default is an empty value set as a double quote ("") The empty value should be added as a single quote on the line value.
ignoreLeadingAndTrailingWhitespace The leading and trailing whitespace is automatic tribber by default. Set it to true to ignore it.
maxCharsPerColum Sets the max chars in the column. The default is 4096.
nullValues Add the ability to set a specific value to the CSv to null.
quoteChar Determine which character will be used to quote the value. You quote the value when you need to add either a space or any char that might fail the process of reading the line. The default is single quotes ('').
textBlock Allows you to use the Text Blocks feature of Java >= 15. It also gives the ability to add comments in the data source which must begin with #
useHeadersInDisplayName Set to true when you want to use a header in the first line. This option is set by default as false.

When you use a configuration the value attribute must be explicitly declared. You see the example in the below extra explanation.

nullValues

Use it when you want to make a specific value to null.

@CsvSource(nullValues = "6.09", value = {
        "Micro SD Card 16Gb, 6.09",
        "JBL GO 2, 22.37",
        "iPad Air Case, 14.99",

})
Enter fullscreen mode Exit fullscreen mode

In the code snippet above you can see that the nullValues is using "6.09". The first value the @CsvSource contains this value, so it will be replaced by a null value instead of using the original one.

textBlock

It allows you to use the Text Blocks feature from Java >= 15, so you don’t need to wrap up the value in the curly brackets. Instead, use the """ as you can see in the example below.

@CsvSource(textBlock = """
        "Micro SD Card 16Gb, 6.09",
        "JBL GO 2, 22.37",
        "iPad Air Case, 14.99"
        """
)
Enter fullscreen mode Exit fullscreen mode

Why using @CsvSource?

Inline CSV data will make the test method more readable and concise, especially when you are dealing with a small number of test cases, so you get the benefit of readability and conciseness.

All test data is directly visible within the test method, making it easy for developers to understand the different scenarios being tested without having to navigate to external files adding immediate visilibity, also reducing external dependencies since the test data is embedded within the source code, you don’t need external CSV files, which can simplify the project structure and reduce dependencies on external resources.

It’s easy to maintain for a small set of test data, helping you to prototype possible scenarios using a CSV file without adding all the dependencies, external files, and complexity.

@CsvFileSource

The @CsvFileSource allows you to provide test data from an external CSV file. This is especially useful when you have a larger set of test data that you want to manage separately from your test code.

class CsvFileSourceTest {

    private static final String MAXIMUM_PRICE = "30.0";

    @DisplayName("Products should not exceed the maximum price")
    @ParameterizedTest(name = "product ''{0}'' of amount ${1} does not exceeds $" + MAXIMUM_PRICE)
    @CsvFileSource(resources = "/products.csv")
    void productsLassThan(String product, BigDecimal amount) {

        assertThat(product).isNotEmpty();
        assertThat(amount).isLessThanOrEqualTo(new BigDecimal(MAXIMUM_PRICE));
    }
}
Enter fullscreen mode Exit fullscreen mode

Line 6 shows the usage of the @CsvFileSource. Not that is using the attribute resources, which will look at the file provided in the resource folders. The default behavior is also to expect the CSV file without a header. You can see a possible file for this test execution:

Micro SD Card 16Gb,6.09
JBL GO 2,22.37
iPad Air Case, 14.99
Enter fullscreen mode Exit fullscreen mode

The particularities

These are the following particularities compared to the @CsvSource.

Same configuration as the @CsvSource

The @CsvFileSource has the same annotation configuration as the @CsvSource, except for the change in the expected quoteChar as it expects double quotes ("") instead of single ones.

Extra configurations

Annotation configuration What it does
files Read the external file as a file, so it requires a full path based on the project classpath.
numLinesToSkip Skip N lines as N is the number of lines to skip. This is useful when you have a header in the file, where you can set it as 1. The default value is 0.
encoding Changes the file encoding. The default value is UTF-8.
resources and files

I believe you noticed that both are in the plural. This means that you can set multiple files. Only single quotes are necessary when you add only one file, as you can see in the previous example.

In case, you want to merge multiple files, simply add the files separated by commas wrapped up in curly brackets.

@DisplayName("Products should not exceed the maximum price")
@ParameterizedTest(name = "product ''{0}'' of amount ${1} does not exceeds $" + MAXIMUM_PRICE)
@CsvFileSource(resources = {"/products.csv", "/products-no.csv"}, numLinesToSkip = 1)
void productsLassThan(String product, BigDecimal amount) {
    assertThat(product).isNotEmpty();
    assertThat(amount).isLessThanOrEqualTo(new BigDecimal(MAXIMUM_PRICE));
}
Enter fullscreen mode Exit fullscreen mode

Line 3 shows the usage of two files: products.csv and products-no.csv.

They must share the same columns you are using in your parameter conversion, the ones you add in your test like String product and BigDecimal amount in this example.

If you use a column and parameter that is present in one file, but not in the other, a ParameterResolutionException will be thrown.

When to use these annotations?

The @CsvFileSource is straightforward: use it when you need to use an external CSV file in your tests.

The author recommends you use the @CsvSource while protecting your data-driven tests using only primitive values or object-related ones like String, Integer, and so on. Otherwise, use any other Source of Arguments provided by JUnit 5.

Top comments (0)