Dependency Injection for Constructors and Methods
In JUnit Jupiter, both test constructors and methods are permitted to have parameters. This allows for greater flexibility and enables Dependency Injection for constructors and methods.
ParameterResolver defines the API for test extensions that wish to dynamically
resolve parameters at runtime. If a test class constructor, a test method, or a
lifecycle method (see Definitions) accepts a parameter, the parameter
must be resolved at runtime by a registered ParameterResolver.
Built-In Parameter Resolvers
There are currently three built-in resolvers that are registered automatically.
TestInfo
If a constructor or method parameter is of type TestInfo, the
TestInfoParameterResolver will supply an instance of TestInfo corresponding to the
current container or test as the value for the parameter. The TestInfo can then be used
to retrieve information about the current container or test such as the display name, the
test class, the test method, and associated tags. The display name is either a technical
name, such as the name of the test class or test method, or a custom name configured via
@DisplayName.
TestInfo acts as a drop-in replacement for the TestName rule from JUnit 4. The
following demonstrates how to have TestInfo injected into a @BeforeAll method, test
class constructor, @BeforeEach method, and @Test method.
-
Java
-
Kotlin
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
@DisplayName("TestInfo Demo")
class TestInfoDemo {
@BeforeAll
static void beforeAll(TestInfo testInfo) {
assertEquals("TestInfo Demo", testInfo.getDisplayName());
}
TestInfoDemo(TestInfo testInfo) {
String displayName = testInfo.getDisplayName();
assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()"));
}
@BeforeEach
void init(TestInfo testInfo) {
String displayName = testInfo.getDisplayName();
assertTrue(displayName.equals("TEST 1") || displayName.equals("test2()"));
}
@Test
@DisplayName("TEST 1")
@Tag("my-tag")
void test1(TestInfo testInfo) {
assertEquals("TEST 1", testInfo.getDisplayName());
assertTrue(testInfo.getTags().contains("my-tag"));
}
@Test
void test2() {
}
}
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeAll
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInfo
@DisplayName("TestInfo Demo")
class TestInfoDemo(
testInfo: TestInfo
) {
companion object {
@JvmStatic
@BeforeAll
fun beforeAll(testInfo: TestInfo) {
assertEquals("TestInfo Demo", testInfo.displayName)
}
}
init {
assertTrue(testInfo.displayName in listOf("TEST 1", "test2()"))
}
@BeforeEach
fun init(testInfo: TestInfo) {
assertTrue(testInfo.displayName in listOf("TEST 1", "test2()"))
}
@Test
@DisplayName("TEST 1")
@Tag("my-tag")
fun test1(testInfo: TestInfo) {
assertEquals("TEST 1", testInfo.displayName)
assertTrue("my-tag" in testInfo.tags)
}
@Test
fun test2() {
}
}
RepetitionInfo
If a method parameter in a @RepeatedTest, @BeforeEach, or @AfterEach method is of
type RepetitionInfo, the RepetitionExtension will supply an instance of
RepetitionInfo. RepetitionInfo can then be used to retrieve information about the
current repetition, the total number of repetitions, the number of repetitions that have
failed, and the failure threshold for the corresponding @RepeatedTest. Note, however,
that RepetitionExtension is not registered outside the context of a @RepeatedTest. See
Repeated Test Examples.
TestReporter
If a constructor or method parameter is of type TestReporter, the
TestReporterParameterResolver will supply an instance of TestReporter. The
TestReporter can be used to publish additional data about the current test run or attach
files to it. The data can be consumed in a TestExecutionListener via the
reportingEntryPublished() or fileEntryPublished() method, respectively. This allows
them to be viewed in IDEs or included in reports.
In JUnit Jupiter you should use TestReporter where you used to print information to
stdout or stderr in JUnit 4. Some IDEs print report entries to stdout or display
them in the user interface for test results.
-
Java
-
Kotlin
class TestReporterDemo {
@Test
void reportSingleValue(TestReporter testReporter) {
testReporter.publishEntry("a status message");
}
@Test
void reportKeyValuePair(TestReporter testReporter) {
testReporter.publishEntry("a key", "a value");
}
@Test
void reportMultipleKeyValuePairs(TestReporter testReporter) {
Map<String, String> values = new HashMap<>();
values.put("user name", "dk38");
values.put("award year", "1974");
testReporter.publishEntry(values);
}
@Test
void reportFiles(TestReporter testReporter, @TempDir Path tempDir) throws Exception {
testReporter.publishFile("test1.txt", MediaType.TEXT_PLAIN_UTF_8, file -> Files.write(file, List.of("Test 1")));
Path existingFile = Files.write(tempDir.resolve("test2.txt"), List.of("Test 2"));
testReporter.publishFile(existingFile, MediaType.TEXT_PLAIN_UTF_8);
testReporter.publishDirectory("test3", dir -> {
Files.write(dir.resolve("nested1.txt"), List.of("Nested content 1"));
Files.write(dir.resolve("nested2.txt"), List.of("Nested content 2"));
});
Path existingDir = Files.createDirectory(tempDir.resolve("test4"));
Files.write(existingDir.resolve("nested1.txt"), List.of("Nested content 1"));
Files.write(existingDir.resolve("nested2.txt"), List.of("Nested content 2"));
testReporter.publishDirectory(existingDir);
}
}
class TestReporterDemo {
@Test
fun reportSingleValue(testReporter: TestReporter) {
testReporter.publishEntry("a status message")
}
@Test
fun reportKeyValuePair(testReporter: TestReporter) {
testReporter.publishEntry("a key", "a value")
}
@Test
fun reportMultipleKeyValuePairs(testReporter: TestReporter) {
testReporter.publishEntry(
mapOf(
"user name" to "dk38",
"award year" to "1974"
)
)
}
@Test
fun reportFiles(
testReporter: TestReporter,
@TempDir tempDir: Path
) {
testReporter.publishFile("test1.txt", MediaType.TEXT_PLAIN_UTF_8) { file ->
Files.write(file, listOf("Test 1"))
}
val existingFile = Files.write(tempDir.resolve("test2.txt"), listOf("Test 2"))
testReporter.publishFile(existingFile, MediaType.TEXT_PLAIN_UTF_8)
testReporter.publishDirectory("test3") { dir ->
Files.write(dir.resolve("nested1.txt"), listOf("Nested content 1"))
Files.write(dir.resolve("nested2.txt"), listOf("Nested content 2"))
}
val existingDir = Files.createDirectory(tempDir.resolve("test4"))
Files.write(existingDir.resolve("nested1.txt"), listOf("Nested content 1"))
Files.write(existingDir.resolve("nested2.txt"), listOf("Nested content 2"))
testReporter.publishDirectory(existingDir)
}
}
Custom Parameter Resolvers
| Custom parameter resolvers must be explicitly enabled by registering appropriate extensions. |
Check out the RandomNumberExtension
for an example of a custom ParameterResolver. While not intended to be production-ready,
it demonstrates the simplicity and expressiveness of both the extension model and the
parameter resolution process. MyRandomParametersTest demonstrates how to inject random
values into a constructor and a @Test method.
-
Java
-
Kotlin
class MyRandomParametersTest {
MyRandomParametersTest(@Random int randomNumber) {
// Use randomNumber in constructor.
}
@Test
void injectsInteger(@Random int i, @Random int j) {
assertNotEquals(i, j);
}
}
class MyRandomParametersTest(
@Random randomNumber: Int
) {
@Test
fun injectsInteger(
@Random i: Int,
@Random j: Int
) {
assertNotEquals(i, j)
}
}
For real-world use cases, check out the source code for the MockitoExtension and the
SpringExtension.
When the type of the parameter to inject is the only condition for your
ParameterResolver, you can use the generic TypeBasedParameterResolver base class.
The supportsParameters method is implemented behind the scenes and supports
parameterized types.