sebastiandaschner blog
First look at JUnit 5
thursday, july 14, 2016In 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 oldorg.junit
-
@BeforeEach
,@BeforeAll
,@AfterEach
,@AfterAll
— instead of the ones known now -
@Disabled
to disable tests — instead of@Ignored
-
@ExtendedWith
supersedes@RunWith
and@Rule
-
DynamicTest
s supersedeParameterized
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: