The purpose of a unit test is to identify the errors in an application as soon as you can. Though several channels might lead you to the same objective, you should aim to use the most efficient route.

A JUnit test suite might have several test classes that need the same data, but you can’t reuse test data. In previous versions of JUnit, a good approach was to create a utility method, then call that method each time a test class needed its data.

JUnit 5 provides a more efficient approach to this problem: dependency injection (DI).

What Is Dependency Injection?

DI is a design pattern where an object supplies the dependencies of another object. When you are building a Java application, you might have a class that depends on an object that another class creates to perform its function.

Before dependency injection, to use an object from a different class you would have to create a new instance of that object within the class that depends on it. So, if you had several classes that depend on the same object, you’d have to create several instances of it within the dependent classes.

DI allows you to use an object in a dependent class, without creating a new instance of it in that class.

Dependency Injection in JUnit 5

JUnit 5 allows you to inject dependencies in both test methods and constructors. This is significant as the previous versions of the framework did not allow test methods or constructors to have parameters.

JUnit 5 allows you to inject as many parameters as you like. The only catch is that the ParameterResolver API must be able to resolve each parameter at run time. JUnit currently has three built-in parameter resolvers that it uses automatically. To use any other resolver, you would need to register it explicitly by using the @ExtendWith annotation.

Injecting Dependencies in JUnit

This sample program uses one of JUnit’s built-in parameters (the TestInfoParameterResolver), to demonstrate how you can inject a dependency into a JUnit 5 test. The TestInfoParameterResolver resolves objects that belong to the TestInfo interface. So, JUnit 5 will supply an instance of the TestInfo interface to any method or constructor that uses it.

        import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
 
class InfoTestInterfaceTest {
    // Injecting a testInfo object into the InfoTestInterfaceTest constructor
    InfoTestInterfaceTest(TestInfo testInfo) {
        assertEquals("InfoTestInterfaceTest", testInfo.getDisplayName());
    }
 
    // Injecting a testInfo object into methods
    @Test
    void testMethodName(TestInfo testInfo) {
        assertEquals("testMethodName(TestInfo)", testInfo.getDisplayName());
    }
 
    @Test
    @DisplayName("method using the @DisplayName annotation")
    void testMethodNameTwo(TestInfo testInfo) {
        assertEquals("method using the @DisplayName annotation", testInfo.getDisplayName());
    }
}

The JUnit test above demonstrates how to inject an object into a constructor and two methods. The JUnit TestInfo interface has four methods that you can use with its object.

The getDisplayName() method is the most useful. It returns the display name of the current test method or constructor. By default, this name is based on the class. But if you use the @DisplayName annotation, the getDisplayName() method will return that text instead.

The test class above generates the following test report:

JUnit dependency injection report

Use DI in @Before and @After Methods

There are four other types of JUnit annotated methods that support dependencies. These are the @BeforeAll, @BeforeEach, @AfterAll, and @AfterEach annotations. Like the @Test method, all you need to do is pass an object to any of the before or after methods as a parameter, and you’re good to go.

The @Before and @After annotations are important, as they also help you to develop more efficient test code. Having the ability to also inject dependencies into these methods will further improve your test code.