Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Parameterized tests

Often one check has to be repeated for many inputs: a slug for ten different titles, a rejection for an empty, blank and null title. Copying the test ten times is a bad idea. A parameterized test is written once, receives the data as method parameters, and JUnit runs it once for every set of values.

The class under test

A slug is the part of a post URL built from its title: Hello, World! becomes hello-world. Create src/main/java/com/example/blog/Slugs.java:

// src/main/java/com/example/blog/Slugs.java
package com.example.blog;
 
import java.util.Locale;
 
public final class Slugs {
 
    private Slugs() {
    }
 
    public static String of(String title) {
        return title.toLowerCase(Locale.ROOT)
                .replaceAll("[^a-z0-9]+", "-")
                .replaceAll("^-|-$", "");
    }
}

Everything except Latin letters and digits turns into a hyphen; hyphens at the edges are removed.

@ValueSource

Instead of @Test you write @ParameterizedTest and add a data source. The simplest one is @ValueSource — an array of values of one type:

// src/test/java/com/example/blog/SlugsTest.java
@ParameterizedTest
@ValueSource(strings = {"Hello", "Hello, World!", "  Java 25  "})
void slugHasNoSpaces(String title) {
    assertFalse(Slugs.of(title).contains(" "));
}

The test runs three times, with a different title each time. Besides strings there are ints, longs, doubles and other arrays. The annotations are imported from org.junit.jupiter.params and org.junit.jupiter.params.provider — the junit-jupiter dependency already contains them.

@CsvSource

When the test needs several parameters — input and expected result — use @CsvSource. Each string is one run, values are separated by commas:

// src/test/java/com/example/blog/SlugsTest.java
@ParameterizedTest
@CsvSource({
        "Hello,           hello",
        "'Hello, World!', hello-world",
        "'  Java 25  ',   java-25",
        "JUnit & Mockito, junit-mockito"
})
void slugFromTitle(String title, String expected) {
    assertEquals(expected, Slugs.of(title));
}

Spaces around values are trimmed, so you can align columns. A value with a comma or with significant spaces is wrapped in single quotes. JUnit converts strings into the parameter types itself: int, long, boolean, BigDecimal, enums.

Empty values and null

@NullAndEmptySource adds two runs: with null and with an empty string. Combined with @ValueSource you get all the "nothing" cases at once:

// src/test/java/com/example/blog/SlugsTest.java
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = {" ", "\t"})
void blankTitleIsRejected(String title) {
    PostService service = new PostService(new InMemoryPostRepository(), (to, message) -> { });
 
    assertThrows(IllegalArgumentException.class, () -> service.create(title, "anna"));
}

Four runs: null, "", a space and a tab. All three tests of the class give Tests run: 11 in the Maven report.

The names of runs

The IDE shows each run separately. JUnit 6 puts string values in quotes, so empty and blank strings are visible: the runs of blankTitleIsRejected are called [1] null, [2] "", [3] " " and [4] "\t". You can set your own format with name:

// src/test/java/com/example/blog/SlugsTest.java
@ParameterizedTest(name = "slug of {0} is {1}")

{0}, {1} are the parameters, {index} is the run number. The runs are now called slug of "Hello" is "hello" and so on. Maven reports name the runs by the method and index: slugFromTitle(String, String)[2].

Common mistake

If you forget the quotes around a value with a comma, the string splits into three columns. JUnit 6 does not complain about the extra column, it just takes the first two:

"Hello, World!, hello-world"
[ERROR] com.example.blog.CsvFailTest.slugFromTitle(String, String)[2] -- Time elapsed: 0.015 s <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: <World!> but was: <hello>

The title became Hello and the expected value became World!. Values with commas always go in single quotes: 'Hello, World!'.

Thanks for reading!

Donate to the site — we work hard for you.