sebastiandaschner blog


First look at JUnit 5

#java #testing thursday, july 14, 2016

In July the first milestone of JUnit 5 was released. This new version will make use of Java 8 lambdas and will redesign some JUnit core features.

What’s new

Some of the main changes in the way how we’ll write JUnit tests are:

  • New package org.junit.jupiter.api — instead of good old org.junit

  • @BeforeEach, @BeforeAll, @AfterEach, @AfterAll — instead of the ones known now

  • @Disabled to disable tests — instead of @Ignored

  • @ExtendedWith supersedes @RunWith and @Rule

  • DynamicTests supersede Parameterized tests

  • Assertions#expectThrows for expected exceptions

The 5.0.0-M1 version can be used from Maven central:

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.0.0-M1</version>
        <scope>test</scope>
    </dependency>
</dependencies>

...

<plugins>
    <plugin>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>2.19.1</version>
        <dependencies>
            <dependency>
                <groupId>org.junit.platform</groupId>
                <artifactId>junit-platform-surefire-provider</artifactId>
                <version>1.0.0-M1</version>
            </dependency>
        </dependencies>
    </plugin>
</plugins>

How to use

Following example of a functionality and its test:

public class FunWithStrings {

    public String getStringLength(final String string) {
        return string + ':' + string.length();
    }

}
public class FunWithStringsTest {

    private FunWithStrings cut = new FunWithStrings();

    // first, simple test
    @Test
    public void testGetStringLengthSimpleTest() {
        assertEquals("hello:5", cut.getStringLength("hello"), () -> "'hello' (length: 5) wasn't calculated properly");
    }

    @TestFactory
    public Stream<DynamicTest> createGetStringLengthTests() {
        final String[][] data = {
                // input, expected
                {"hello", "hello:5"},
                {"hel", "hel:3"},
                {"h", "h:1"},
                {"", ":0"},
                {" ", " :1"}
        };

        return Stream.of(data).map(o -> dynamicTest("test: " + o[0], () -> assertEquals(o[1], cut.getStringLength(o[0]))));
    }

    @Test
    public void testNull() {
        expectThrows(NullPointerException.class, () -> cut.getStringLength(null));
    }

}

The method annotated with @TestFactory is not a test itself but creates a stream of dynamic tests — in this usage similar to the idea of Parameterized tests of JUnit 4.

The following example shows how more sophisticated test scenarios could be realized. Imagine a system test scenario testing a resource which is available only after some startup time. The test should wait within a timeout for the resource being available.

Prior to JUnit 5 this could be realized with an @Rule.

@ExtendWith(MockedSystemExtension.class)
public class SystemTest {

    @Test
    public void test(final MockedSystem mockedSystem) {
        final Response response = mockedSystem.target().request().get();

        assertEquals(response.getStatusInfo().getFamily(), Response.Status.Family.SUCCESSFUL, () -> "status code is not 2xx");
        assertEquals(response.getHeaderString("X-Hello"), "World", () -> "header 'X-Hello' not correct");
    }

}

The MockedSystem can be injected in the test method due to the registered extension:

public class MockedSystemExtension implements ParameterResolver {

    @Override
    public boolean supports(final ParameterContext parameterContext, final ExtensionContext extensionContext) throws ParameterResolutionException {
        return parameterContext.getParameter().getType().isAssignableFrom(MockedSystem.class);
    }

    @Override
    public Object resolve(final ParameterContext parameterContext, final ExtensionContext extensionContext) throws ParameterResolutionException {
        final MockedSystem mockedSystem = new MockedSystem();

        waitForStartUp(mockedSystem);

        return mockedSystem;
    }

    private void waitForStartUp(MockedSystem mockedSystem) {
        // ...
    }

}

The system simulates a WebTarget which normally would be used to access an API.

Here I use Mockito to simulate a system which takes 10 seconds to start up.

public class MockedSystem {

    private static final int STARTUP_TIME = 10;

    private final WebTarget target;

    MockedSystem() {
        final WebTarget tut = mock(WebTarget.class);
        final Invocation.Builder builder = mock(Invocation.Builder.class);
        final Response notFoundResponse = mock(Response.class);
        final Response okResponse = mock(Response.class);
        final long start = System.currentTimeMillis();

        when(tut.request()).thenReturn(builder);
        when(builder.get()).then(a -> {
            if (System.currentTimeMillis() - start < STARTUP_TIME * 1000)
                return notFoundResponse;
            return okResponse;
        });

        when(notFoundResponse.getStatus()).thenReturn(404);
        when(notFoundResponse.getStatusInfo()).thenReturn(Response.Status.NOT_FOUND);

        when(okResponse.getStatus()).thenReturn(200);
        when(okResponse.getStatusInfo()).thenReturn(Response.Status.OK);
        when(okResponse.getHeaders()).thenReturn(new MultivaluedHashMap<>(Collections.singletonMap("X-Hello", "World")));
        when(okResponse.getHeaderString(anyString())).then(a -> ((Response) a.getMock()).getHeaders().getFirst(a.getArgument(0)));

        this.target = tut;
    }

    public WebTarget target() {
        return target;
    }

}

Therefore the SystemTest can rely that the injected MockedSystem was probed to be available before — transparent to the test class itself.

Further reading

These snippets are taken from my JUnit 5 Playground GitHub project.

Also see the JUnit 5 User Guide.

Happy testing!

 

Found the post useful? Subscribe to my newsletter for more free content, tips and tricks on IT & Java: