In JUnit 5, there are different rules relative to the use of annotations in Java interfaces. First of all, we need to be aware that @Test, @TestFactory, @BeforeEach, and @AfterEach can be declared on interface default methods.
The second rule regarding JUnit 5 and interfaces is that @BeforeAll and @AfterAll can be declared on static methods in a test interface. Moreover, if the test class, which implements a given interface, is annotated with @TestInstance(Lifecycle.PER_CLASS), the methods @BeforeAll and @AfterAll declared on the interface do not need to be static, but default methods.
The third and final rule concerning interfaces in JUnit 5 is @ExtendWith and @Tag can be declared on test interfaces to configure extensions and tags.
Let's see some simple examples. In the following class, we are creating an interface, not a class. In this interface, we use the annotations @BeforeAll, @AfterAll, @BeforeEach, and @AfterEach. On the one hand, we define @BeforeAll, @AfterAll as static methods. On the other hand, we are defining @BeforeEach and @AfterEach as Java 8 default methods:
package io.github.bonigarcia;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.TestInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public interface TestLifecycleLogger {
static final Logger log = LoggerFactory
.getLogger(TestLifecycleLogger.class.getName());
@BeforeAll
static void beforeAllTests() {
log.info("beforeAllTests");
}
@AfterAll
static void afterAllTests() {
log.info("afterAllTests");
}
@BeforeEach
default void beforeEachTest(TestInfo testInfo) {
log.info("About to execute {}", testInfo.getDisplayName());
}
@AfterEach
default void afterEachTest(TestInfo testInfo) {
log.info("Finished executing {}", testInfo.getDisplayName());
}
}
In this example, we are using the annotation TestFactory to define a default method in a Java interface:
package io.github.bonigarcia;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
import java.util.Arrays;
import java.util.Collection;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
interface TestInterfaceDynamicTestsDemo {
@TestFactory
default Collection<DynamicTest> dynamicTestsFromCollection() {
return Arrays.asList(
dynamicTest("1st dynamic test in test interface",
() -> assertTrue(true)),
dynamicTest("2nd dynamic test in test interface",
() -> assertTrue(true)));
}
}
Finally, we use the annotation @Tag and @ExtendWith in another interface:
package io.github.bonigarcia;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.extension.ExtendWith;
@Tag("timed")
@ExtendWith(TimingExtension.class)
public interface TimeExecutionLogger {
}
All in all, we can use these interfaces in our Jupiter tests:
package io.github.bonigarcia;
import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;
class TestInterfaceTest implements TestLifecycleLogger,
TimeExecutionLogger,
TestInterfaceDynamicTestsDemo {
@Test
void isEqualValue() {
assertEquals(1, 1);
}
}
In this test, the fact of implementing all the previously defined interfaces will provide the logging capabilities implemented in the default methods: