Table of contents
1.
Introduction
2.
The Problem
2.1.
Program
2.2.
Discussion
3.
Test Interfaces and Default Methods
3.1.
Program
3.2.
Program
3.3.
Output
4.
FAQs
5.
Key Takeaways
Last Updated: Mar 27, 2024

JUnit Tests Interfaces and Default Methods

Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

In this blog we will discuss how to test interfaces and default methods in Java using JUnit.  You can follow this article to get started with interfaces in Java. In this blog, we will see how to write JUnit test cases for interface default methods. We will first discuss the conceptual problem associated with testing interfaces and default methods in Java 8 or above. We will then discuss the solution provided by JUnit especially for this purpose.

If you wish to get started with JUnit, you can follow this article from Coding Ninjas especially created for getting started with JUnit.

The Problem

In Java 8, you may set default method implementations in interfaces, which is a very handy feature. This makes things much easier because you would normally have to create an abstract class to hold this default sort of behaviour (in older versions of Java), which means you lose the ability to extend any other classes. So let us discuss the problem with an example.

Program

public interface MyInterface {
 ObjectProperty<String> ageProperty();

 default String convertToTitleCase(String textToConvert) {
   return (
     textToConvert.substring(0, 1).toUpperCase() +
     textToConvert.substring(1).toLowerCase()
   );
 }
}
You can also try this code with Online Java Compiler
Run Code

Discussion

How can you test this default function without testing something that implements this interface in your production code? This is where it gets a little complicated. For two reasons, you should avoid testing this with real implementations.

  • The default method may be overridden by the real implementation (you would need to test this separately then anyway).
  • You could elect not to implement the interface in this implementation or simply delete the class.

Making a test implementation of this class and testing the default method on this test implementation is the simplest way to work around these concerns. This test implementation does nothing but implement the interface and leave the abstract methods unfilled.

Test Interfaces and Default Methods

On interface default methods, JUnit Jupiter allows you to specify @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @TestTemplate, @BeforeEach, and @AfterEach.

If the test interface or test class is annotated with @TestInstance(Lifecycle.PER CLASS), @BeforeAll and @AfterAll can be declared on static methods in a test interface or on interface default methods.

On a test interface, @ExtendWith and @Tag can be defined so that classes that implement the interface inherit the tags and extensions automatically.

Finally, you can implement these test interfaces in your test class to have them applied. Let us create a demo example to test an interface and default method using JUnit.

Program

@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))));
   }

}
You can also try this code with Online Java Compiler
Run Code

You can implement these test interfaces in your test class to have them used.

Program

class TestInterfaceDemo implements TestLifecycleLogger,
       TimeExecutionLogger, TestInterfaceDynamicTestsDemo {

   @Test
   void isEqualValue() {
       assertEquals(1, "a".length(), "is always equal");
   }

}
You can also try this code with Online Java Compiler
Run Code

Output

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

FAQs

  1. Are interface methods public by default?
    You can skip the public modifier since all abstract, default, and static methods in an interface are automatically public. Constant declarations can also be found in interfaces. In an interface, all constant values are implicitly public, static, and final.
     
  2. How is the interface used in unit testing?
    You can use an abstract test case to test an interface with common tests independent of implementation, and then generate concrete instances of the test case for each implementation of the interface.
     
  3. Can we apply @BeforeAll and @Afterall annotations before all methods in a test interface?
    If the test interface or test class is annotated with @TestInstance(Lifecycle.PER CLASS), @BeforeAll and @AfterAll can be declared on static methods in a test interface or on interface default methods.
    Check out JUnit Interview Questions here.

Key Takeaways

In this blog we discussed thoroughly about testing interfaces and default methods in JUnit. Testing the logic of default methods in interfaces can be of utmost importance in big projects. At the same time, creating implementations of interfaces and then testing them is cumbersome and time consuming. Thus, we discussed a solution from JUnit to avoid such situations. We then discussed an example revolving around the same solution. We saw different annotations and the methods before which they can be applied.


Yet there is a lot more to learn. Learning never stops, and to feed your quest to learn and become more skilled, head over to our practice platform Coding Ninjas Studio to practice top problems, attempt mock tests, read interview experiences, and much more.! 

Live masterclass