2.13. Test Interfaces and Default Methods
JUnit Jupiter allows @Test
, @RepeatedTest
, @ParameterizedTest
, @TestFactory
, @TestTemplate
, @BeforeEach
, and @AfterEach
to be declared on interface default
methods. @BeforeAll
and @AfterAll
can either be declared on static
methods in a test interface or on interface default
methods if the test interface or test class is annotated with @TestInstance(Lifecycle.PER_CLASS)
(see Test Instance Lifecycle). Here are some examples.
@TestInstance(Lifecycle.PER_CLASS)
interface TestLifecycleLogger {
static final Logger logger = Logger.getLogger(TestLifecycleLogger.class.getName());
@BeforeAll
default void beforeAllTests() {
logger.info("Before all tests");
}
@AfterAll
default void afterAllTests() {
logger.info("After all tests");
}
@BeforeEach
default void beforeEachTest(TestInfo testInfo) {
logger.info(() -> String.format("About to execute [%s]",
testInfo.getDisplayName()));
}
@AfterEach
default void afterEachTest(TestInfo testInfo) {
logger.info(() -> String.format("Finished executing [%s]",
testInfo.getDisplayName()));
}
}
interface TestInterfaceDynamicTestsDemo {
@TestFactory
default Stream<DynamicTest> dynamicTestsForPalindromes() {
return Stream.of("racecar", "radar", "mom", "dad")
.map(text -> dynamicTest(text, () -> assertTrue(isPalindrome(text))));
}
}
@ExtendWith
and @Tag
can be declared on a test interface so that classes that implement the interface automatically inherit its tags and extensions. See Before and After Test Execution Callbacks for the source code of the TimingExtension.
@Tag("timed")
@ExtendWith(TimingExtension.class)
interface TimeExecutionLogger {
}
In your test class you can then implement these test interfaces to have them applied.
class TestInterfaceDemo implements TestLifecycleLogger,
TimeExecutionLogger, TestInterfaceDynamicTestsDemo {
@Test
void isEqualValue() {
assertEquals(1, "a".length(), "is always equal");
}
}
Running the TestInterfaceDemo
results in output similar to the following:
INFO example.TestLifecycleLogger - Before all tests
INFO example.TestLifecycleLogger - About to execute [dynamicTestsForPalindromes()]
INFO example.TimingExtension - Method [dynamicTestsForPalindromes] took 19 ms.
INFO example.TestLifecycleLogger - Finished executing [dynamicTestsForPalindromes()]
INFO example.TestLifecycleLogger - About to execute [isEqualValue()]
INFO example.TimingExtension - Method [isEqualValue] took 1 ms.
INFO example.TestLifecycleLogger - Finished executing [isEqualValue()]
INFO example.TestLifecycleLogger - After all tests
Another possible application of this feature is to write tests for interface contracts. For example, you can write tests for how implementations of Object.equals
or Comparable.compareTo
should behave as follows.
public interface Testable<T> {
T createValue();
}
public interface EqualsContract<T> extends Testable<T> {
T createNotEqualValue();
@Test
default void valueEqualsItself() {
T value = createValue();
assertEquals(value, value);
}
@Test
default void valueDoesNotEqualNull() {
T value = createValue();
assertFalse(value.equals(null));
}
@Test
default void valueDoesNotEqualDifferentValue() {
T value = createValue();
T differentValue = createNotEqualValue();
assertNotEquals(value, differentValue);
assertNotEquals(differentValue, value);
}
}
public interface ComparableContract<T extends Comparable<T>> extends Testable<T> {
T createSmallerValue();
@Test
default void returnsZeroWhenComparedToItself() {
T value = createValue();
assertEquals(0, value.compareTo(value));
}
@Test
default void returnsPositiveNumberWhenComparedToSmallerValue() {
T value = createValue();
T smallerValue = createSmallerValue();
assertTrue(value.compareTo(smallerValue) > 0);
}
@Test
default void returnsNegativeNumberWhenComparedToLargerValue() {
T value = createValue();
T smallerValue = createSmallerValue();
assertTrue(smallerValue.compareTo(value) < 0);
}
}
In your test class you can then implement both contract interfaces thereby inheriting the corresponding tests. Of course you’ll have to implement the abstract methods.
class StringTests implements ComparableContract<String>, EqualsContract<String> {
@Override
public String createValue() {
return "banana";
}
@Override
public String createSmallerValue() {
return "apple"; // 'a' < 'b' in "banana"
}
@Override
public String createNotEqualValue() {
return "cherry";
}
}
The above tests are merely meant as examples and therefore not complete. |