Overview & new features
by Vlad Gaevsky / EPAM Systems
IntelliJ IDEA 2016.3.1+
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.0.0-M3</version>
</dependency>
Other IDE
+
Plugin
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>
junit-platform-surefire-provider
</artifactId>
<version>1.0.0-M3</version>
</dependency>
</dependencies>
</plugin>
dependencies {
testCompile 'org.junit.jupiter:junit-jupiter-api:5.0.0-M3'
}
JUnit 5 | JUnit 4 |
---|---|
@BeforeEach | @Before |
@AfterEach | @After |
@BeforeAll | @BeforeClass |
@AfterAll | @AfterClass |
@Disabled | @Ignore |
@Test
JUnit 5
JUnit 4
org.junit.jupiter.api.Test
org.junit.Test
class FirstTests {
@Test
void firstTest() {
fail();
}
}
4.. or 5?
No more public!
@Test (timeout=..., expected=...)
public interface ComparableContract<T extends Comparable<T>> {
T createValue();
T createSmallerValue();
@Test
default void returnsZeroWhenComparedToItself() {
T value = createValue();
assertEquals(0, value.compareTo(value));
}
@Test
default void returnsPositiveNumberComparedToSmallerValue() {
T value = createValue();
T smallerValue = createSmallerValue();
assertTrue(value.compareTo(smallerValue) > 0);
}
@Test
default void returnsNegativeNumberComparedToSmallerValue() {
T value = createValue();
T smallerValue = createSmallerValue();
assertTrue(smallerValue.compareTo(value) < 0);
}
}
@Tag("Test class")
public class TaggedTest {
@Test
@Tag("Test method")
void testMethod() {
assertEquals(2+2, 4);
}
@Test
@Tag("jenkins")
void jenkinsOnly() {
fail();
}
}
apply plugin: 'org.junit.platform.gradle.plugin'
junitPlatform {
filters {
tags {
include 'jenkins'
}
}
}
@Test
@DisplayName("Custom test name containing spaces")
void testWithDisplayNameContainingSpaces() {
}
@Test
@DisplayName("╯°□°)╯")
void testWithDisplayNameContainingSpecialCharacters() {
}
@Test
@DisplayName("😱")
void testWithDisplayNameContainingEmoji() {
}
@DisplayName("A stack")
class TestingAStackDemo {
Stack<Object> stack;
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isEmpty() {
assertFalse(stack.isEmpty());
}
}
}
}
Before each in WhenNew
Before each in AfterPushing
@Rule
@ClassRule
@RunWith
JUnit has two competing extension mechanisms, each with its own limitations.
Rules
Runners
@ExtendWith(MockitoExtension.class)
@Test
void mockTest() {
// ...
}
@ExtendWith({ FooExtension.class, BarExtension.class })
class MyTestsV1 {
// ...
}
@ExtendWith(MockitoExtension.class)
class MockTests {
// ...
}
Method level
Class level
Multiple extensions!
BeforeAllCallback
BeforeEachCallback
BeforeTestExecutionCallback
AfterTestExecutionCallback
AfterEachCallback
AfterAllCallback
public class MockitoExtension implements TestInstancePostProcessor, ParameterResolver {
@Override
public void postProcessTestInstance(Object testInstance, ExtensionContext context) {
MockitoAnnotations.initMocks(testInstance);
}
@Override
public boolean supports(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return parameterContext.getParameter().isAnnotationPresent(Mock.class);
}
@Override
public Object resolve(ParameterContext parameterContext,
ExtensionContext extensionContext) {
return getMock(parameterContext.getParameter(), extensionContext);
}
//...
}
@ExtendWith(MockitoExtension.class)
class MockitoDemoTest {
@BeforeEach
void init(@Mock Person person) {
when(person.getName()).thenReturn("Dilbert");
}
@Test
void simpleTestWithInjectedMock(@Mock Person person) {
assertEquals("Dilbert", person.getName());
}
}
class BuiltInResolversDemo {
@Test
@DisplayName("TEST 1")
@Tag("my tag")
void test1(TestInfo testInfo) {
assertEquals("TEST 1", testInfo.getDisplayName());
assertTrue(testInfo.getTags().contains("my tag"));
}
@Test
void reportSingleValue(TestReporter testReporter) {
testReporter.publishEntry("a key", "a value");
}
}
class DisabledCondition implements ContainerExecutionCondition, TestExecutionCondition {
/**
* Tests are disabled if {@code @Disabled} is present on the test method.
*/
@Override
public ConditionEvaluationResult evaluate(TestExtensionContext context) {
Optional<Disabled> disabled =
findAnnotation(context.getElement(), Disabled.class);
if (disabled.isPresent()) {
String reason = disabled.map(Disabled::value)
.filter(StringUtils::isNotBlank)
.orElseGet(() -> element.get() + " is @Disabled");
return ConditionEvaluationResult.disabled(reason);
}
return ConditionEvaluationResult.enabled("@Disabled is not present");
}
//The same for ContainerExecutionCondition
}
ContainerExecutionCondition and TestExecutionCondition define the Extension APIs for programmatic, conditional test execution.
class DynamicTestsDemo {
@TestFactory
Collection<DynamicTest> dynamicTestsFromCollection() {
return Arrays.asList(
dynamicTest("1st dynamic test", () -> assertTrue(true)),
dynamicTest("2nd dynamic test", () -> assertEquals(4, 2 * 2))
);
}
@TestFactory
Stream<DynamicTest> dynamicTestsFromIntStream() {
// Generates tests for the first 10 even integers.
return IntStream.iterate(0, n -> n + 2)
.limit(10)
.mapToObj(n ->
dynamicTest("test" + n, () -> assertTrue(n % 2 == 0))
);
}
}
class DynamicTestsDemo {
@TestFactory
Stream<DynamicTest> generateRandomNumberOfTests() {
// Generates random positive integers between 0 and 100 until
// a number evenly divisible by 7 is encountered.
Iterator<Integer> inputGenerator = new Iterator<Integer>() {
Random random = new Random();
int current;
@Override
public boolean hasNext() {
current = random.nextInt(100);
return current % 7 != 0;
}
@Override
public Integer next() {
return current;
}
};
// Returns a stream of dynamic tests.
return DynamicTest.stream(
inputGenerator,
(input) -> "input:" + input, //display name generator
(input) -> assertTrue(input % 7 != 0) //test executor
);
}
}
@Test
public void windowsOnly() {
assumeTrue(System.getenv("OS").startsWith("Windows"));
// ...
}
@Test
public void windowsOnlyAction() {
assumingThat(System.getenv("OS").startsWith("Windows"),
() -> {
System.out.println("Wow, it's Windows!'");
});
// ...
}
@Test
public void assertions() {
//classic assertEquals
assertEquals("Java", meetup.getLanguage());
//lambda for message
assertTrue(2 == 2, () -> "Assertion messages can be lazily evaluated -- "
+ "to avoid constructing complex messages unnecessarily.");
//lambda for condition
assertTrue(() -> "".isEmpty(), "string should be empty");
// In a grouped assertion all assertions are executed, and any
// failures will be reported together.
assertAll(
() -> assertEquals("John", address.getFirstName()),
() -> assertEquals("Doe", address.getLastName())
);
}
@Test
void timeoutNotExceeded() {
assertTimeout(ofMinutes(2), () -> {
// Perform task that takes less than 2 minutes.
});
}
@Test
void timeoutExceeded() {
// execution exceeded timeout of 10 ms by 91 ms
assertTimeout(ofMillis(10), () -> {
Thread.sleep(100);
});
}
@Test
void timeoutExceededWithPreemptiveTermination() {
// execution timed out after 10 ms
assertTimeoutPreemptively(ofMillis(10), () -> {
Thread.sleep(100);
});
}
@Test
void exceptionTesting() {
Throwable exception = assertThrows(IllegalArgumentException.class, () -> {
throw new IllegalArgumentException("a message");
});
assertEquals("a message", exception.getMessage());
}
The only thing you need:
junit-vintage-engine
JUnit 5 = Platform + Jupiter + Vintage
Presentation
JUnit 5 Reference
Code