JUnit 5 Jupiter Testing

Author: Shazin Sadakath


JUnit has been around forever and it is almost the defacto testing framework for Java Developers. But last version of JUnit 4 was released almost 10 years ago which is a quite a long time and a lot has changed since then. 

With the introduction of Java 8 Lambdas, Java 9 Modules, etc. JUnit required a major revamp of the framework. For this reason JUnit team has released its major version 5 nicknamed Jupiter. Major goals of JUnit 5 Jupiter has been to achieve the following:

JUnit 5 is composed of following modules:

Let's look at a small example of a FormatService:

public class FormatService {

    public String append(String main, String suffix) {
        if (main == null || suffix == null) {
            throw new IllegalArgumentException("Both Arguments are required");
        }
        return main + suffix;
    }

    public String prepend(String main, String prefix) {
        if (main == null || prefix == null) {
            throw new IllegalArgumentException("Both Arguments are required");
        }
        return prefix + main;
    }

    public String repeat(String main, int numberOfTimes) {
        if (main == null || numberOfTimes == 0) {
            throw new IllegalArgumentException("Both Arguments are required");
        }
        List items = Collections.nCopies(numberOfTimes, main);
        return String.join("", items);
    }

}

and how it is tested using JUnit 4:

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.Timeout;

import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertEquals;

public class JUnitFormatServiceTest {

    private FormatService formatService = new FormatService();

    @Rule
    public ExpectedException expectedException = ExpectedException.none();

    @Rule
    public Timeout timeout = new Timeout(15, TimeUnit.MILLISECONDS);

    @Test
    public void testAppend() {
        assertEquals("abcdef", formatService.append("abc", "def"));
    }

    @Test
    public void testPrepend() {
        assertEquals("uvwxyz", formatService.prepend("xyz", "uvw"));
    }

    @Test
    public void testRepeat() {
        assertEquals("blahblahblah", formatService.repeat("blah", 3));
    }

    @Test
    public void testRepeat_timeout() {
        formatService.repeat("blah", 100_000);
    }

    @Test
    public void testAppend_IllegalArgumentException() {
        expectedException.expect(IllegalArgumentException.class);
        formatService.append(null, null);
    }
}

This style of testing may be really familiar to many as it uses org.junit.Test annotation and org.junit.Rule annotation along with static methods in org.junit.Assert class to carry out testing scenarios. Some restrictions of Junit 4 are that it required to have public class for test case as well as public methods for tests. This is not mandatory in JUnit 5 as we will see later. 

Now lets see how to migrate this to a Test case which can be run with JUnit 5 with minimum changes:

import org.junit.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport;

import java.time.Duration;

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

@EnableRuleMigrationSupport
public class JUnitJupiterMigrationFormatServiceTest {

    private FormatService formatService = new FormatService();

    @Test
    public void testAppend() {
        assertEquals("abcdef", formatService.append("abc", "def"));
    }

    @Test
    public void testPrepend() {
        assertEquals("uvwxyz", formatService.prepend("xyz", "uvw"));
    }

    @Test
    public void testRepeat() {
        assertEquals("blahblahblah", formatService.repeat("blah", 3));
    }

    @Test
    @DisplayName("The Test Didn't finish within the specified time")
    public void testRepeat_timeout() {
        assertTimeout(Duration.ofMillis(10), () -> formatService.repeat("blah", 100_000));
    }

    @Test
    public void testAppend_IllegalArgumentException() {
        IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> formatService.append(null, null));
        assertNotNull(iae);
        assertEquals("Both Arguments are required", iae.getMessage());
    }
}

As you can see here the code is still almost the same except for the org.junit.jupiter.migrationsupport.rules.EnableRuleMigrationSupport annotation and using of static methods from class org.junit.jupiter.api.Assertions. Now this Test does exactly samething using JUnit 5. Some other important things to note are the usage of assertTimeout and assertThrows methods instead of @Rule annotations.

Following shows an 100% JUnit 5 test case with some of its most notable features:

import org.junit.jupiter.api.*;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.Collections;
import java.util.stream.Stream;

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

class JUnitJupiterFormatServiceTests {

    private FormatService formatService = new FormatService();

    @Test
    @DisabledOnOs(OS.WINDOWS)
    @DisplayName("Not supposed to run on Windows OS")
    void testAppend() {
        assertEquals("abcdef", formatService.append("abc", "def"));
    }

    @Nested
    @DisplayName("Happy Cases")
    public class HappyCases {
        @Test
        @Tag("happy")
        void testAppend() {
            assertEquals("abcdef", formatService.append("abc", "def"));
        }

        @Test
        @Tag("happy")
        void testPrepend() {
            assertEquals("uvwxyz", formatService.prepend("xyz", "uvw"));
        }

        @Test
        @Tag("happy")
        void testRepeat() {
            assertEquals("blahblahblah", formatService.repeat("blah", 3));
        }

    }

    @RepeatedTest(value = 5, name = "පරීක්ෂණ {totalRepetitions} න් {currentRepetition} වන පරීක්ෂණය")
    void repeatingTest(RepetitionInfo repetitionInfo) {
        assertEquals(String.join("", Collections.nCopies(repetitionInfo.getCurrentRepetition(), "clap")), formatService.repeat("clap", repetitionInfo.getCurrentRepetition()));
    }

    @ParameterizedTest
    @ValueSource(strings = {
        " World!",
        " John",
        " Tom"
    })
    void parameterizedTest(String input) {
        assertEquals("Hello"+input, formatService.append("Hello", input));
    }

    @TestFactory
    Stream dynamicTests() {
        final String FILE_NAME = "abc";
        return Stream.of(".jpg", ".png", "tif").map(s -> dynamicTest("Test for "+s, () -> assertEquals(FILE_NAME+s, formatService.append(FILE_NAME, s))));
    }

}

JUnit 5 features in detail:

The source code can be found at Github



Tags: JUnit5 Jupiter Testing
Views: 794
Register for more exciting articles

Comments

Please login or register to post a comment.


There are currently no comments.