Table of contents
1.
Introduction
2.
ParameterResolver
2.1.
TestInfoParameterResolver
2.2.
RepetitionInfoParameterResolver
2.3.
TestReporterParameterResolver
3.
Example of Custom ParameterResolver
4.
Repeated tests
5.
Parameterized tests
6.
FAQs
7.
Key Takeaways
Last Updated: Mar 27, 2024

JUnit5 Dependency Injection for constructors and Methods

Author Toohina Barua
0 upvote
Career growth poll
Do you think IIT Guwahati certified course can help you in your career?

Introduction

Test constructors or methods were not permitted to contain arguments in any previous JUnit version (at least not with the standard Runner implementations). Both test constructors and methods can now contain parameters, which is one of the primary improvements in JUnit Jupiter. This gives you more freedom and allows you to use Dependency Injection with constructors and methods.

ParameterResolver

ParameterResolver is an API for test extensions that resolve arguments dynamically during runtime. If a test class constructor, a test method, or a lifecycle method accepts a parameter, the parameter must be resolved at runtime by a registered ParameterResolver.
There are three built-in resolvers that are automatically registered at the moment:

  • TestInfoParameterResolver
  • RepetitionInfoParameterResolver
  • TestReporterParameterResolver

TestInfoParameterResolver

The TestInfoParameterResolver will give an instance of TestInfo corresponding to the current container or test as the value for the parameter if a constructor or method parameter is of type TestInfo. The TestInfo may then be used to get information about the current container or test, including the display name, test class, test method, and related tags. A technical name, such as the name of the test class or test method, or a custom name set using @DisplayName, is used as the display name.
TestInfo is a drop-in replacement for the JUnit 4 TestName rule. TestInfo may be injected into a test constructor, @BeforeEach method, and @Test method as seen below.


import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

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 {

    TestInfoDemo(TestInfo testInfo) {
        assertEquals("TestInfo Demo", testInfo.getDisplayName());
    }

    @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() {
    }

RepetitionInfoParameterResolver

The RepetitionInfoParameterResolver will supply an instance of RepetitionInfo if a method parameter in a @RepeatedTest, @BeforeEach, or @AfterEach method is of type RepetitionInfo. The information about the current repetition and the overall number of repetitions for the relevant @RepeatedTest can then be retrieved using RepetitionInfo. Outside of the context of a @RepeatedTest, however, RepetitionInfoParameterResolver is not registered.

TestReporterParameterResolver

The TestReporterParameterResolver will provide an instance of TestReporter if a constructor or method parameter is of type TestReporter. Additional statistics about the current test run can be published using the TestReporter. The data may be examined in IDEs or included in reports thanks to the reportingEntryPublished() function in a TestExecutionListener.
Where you used to print information to stdout or stderr in JUnit 4, you should use TestReporter in JUnit Jupiter. All reported items will be written to stdout if @RunWith(JUnitPlatform.class) is used. In addition, some IDEs publish report items to stdout or display them as test results in the user interface.

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

}

Example of Custom ParameterResolver

RandomParametersExtension, an example of a custom parameterResolver, is shown below. While not designed for production, it highlights the extension model's and parameter resolution process's simplicity and expressiveness. MyRandomParametersTest shows how to inject random values in @Test methods.

@ExtendWith(RandomParametersExtension.class)
class MyRandomParametersTest {

    @Test
    void injectsInteger(@Random int i, @Random int j) {
        assertNotEquals(i, j);
    }

    @Test
    void injectsDouble(@Random double d) {
        assertEquals(0.0, d, 1.0);
    }

}

Repeated tests

By annotating a method with @RepeatedTest and giving the total number of repetitions requested, JUnit 5 allows you to repeat a test a given number of times. This is especially handy when certain conditions change from one test execution to the next. Each invocation of a repeated test is treated as if it were a standard @Test method, complete with lifecycle callbacks and extensions.
The name element of the @RepeatedTest annotation may be used to give a custom display name for each repetition in addition to the number of repeats. Currently, the following placeholders are supported:

– displayName: the method's display name 

– currentRepetition: the current repetition number 

– totalRepetitions: the total number of repetitions

The following code demonstrates how to use repeated tests, display name placeholders, and the RepetitionInfo arguments. The scenario of the first repeated test ensures that the execution of the Calculator class's add function is consistent and produces the same result every time. The second repeating test case ensures that collections behave correctly: a list receives a new member with each iteration, but a set will not receive duplicate items, even if we try to insert it several times.

public class RepeatedTestsTest {

    private static Set<Integer> integerSet = new HashSet<>();
    private static List<Integer> integerList = new ArrayList<>();

    @RepeatedTest(value = 5, name =                                           //(1)
    "{displayName} - repetition {currentRepetition} of {totalRepetitions}")                 //(1)
    @DisplayName("Test add operation")
    void addNumber() {
        Calculator calculator = new Calculator();
        assertEquals(2, calculator.add(1, 1),
        "1 + 1 should equal 2");
    }
    @RepeatedTest(value = 5, name = "the list contains //(2)
    {currentRepetition} elements(s), the set contains 1 element") //(2)
    void testAddingToCollections(TestReporter testReporter,
    RepetitionInfo repetitionInfo) {
        integerSet.add(1);
        integerList.add(repetitionInfo.getCurrentRepetition());
        testReporter.publishEntry("Repetition number", //(3)
        String.valueOf(repetitionInfo.getCurrentRepetition())); //(3)
        assertEquals(1, integerSet.size());
        assertEquals(repetitionInfo.getCurrentRepetition(),
        integerList.size());
    }
}

We make the following observations in light of the preceding example:

  • The initial test is repeated five times, with the display name, current repetition number, and a total number of repetitions displayed at each repetition (1).
  • The second test is repeated five times, with the number of elements in the list (which is the current repetition number) and the fact that the set always includes just one element displayed at each repetition (2).
  • The repetition number is displayed throughout the execution of each of the repeated second tests, as it is inserted into the RepetitionInfo parameter (3).

The result of the execution of the previous tests is shown in the images below:

The names of the repeated tests at the time of the execution

 The messages are shown into the console by the second repeated test

Parameterized tests

Parameterized tests allow you to execute a test with different arguments numerous times. The main advantage is that you can create just one test and have it run on a variety of different arguments. Your tests will be safer, and you'll be able to verify a variety of input data. @ParameterizedTest is used to annotate the methods. At least one source must be declared, which gives the parameters for each invocation and subsequently consumes them in the test function.
The simplest of the potential sources is @ValueSource. To provide a single argument per parameterized test invocation, you must supply a single array of literal values. The code below demonstrates how to use this annotation. The test scenario aims to determine the number of words in a set of supplied phrases, which are provided as parameters.

class ParameterizedWithValueSourceTest {
    private WordCounter wordCounter = new WordCounter();

    @ParameterizedTest //(1)
    @ValueSource(strings = {"Check three parameters", //(2)
    "JUnit in Action"}) //(2)
   
    void testWordsInSentence(String sentence) {
        assertEquals(3, wordCounter.countWords(sentence));
    }
}

We see in the preceding example:

  • Using the appropriate annotation, we designate the test as parameterized (1).
  • The values to be supplied as parameters to the testing method are then specified (2). The testing function is called twice, once for each of the arguments supplied by the @ValueSource annotation and once for each of the arguments provided by the @ValueSource annotation.

FAQs

  1. What should we do if the type of parameter to inject is the only condition for your ParameterResolver?
    You can use the generic TypeBasedParameterResolver base class when the type of the parameter to inject is the only criteria for your parameterResolver. The supportsParameters function is used to support parameterized types and is implemented behind the scenes.
     
  2. How are some parameter resolvers explicitly enabled?
    Some parameter resolvers must be explicitly enabled by registering appropriate extensions via @ExtendWith.
     
  3. What is @test (test annotation) in JUnit?
    JUnit recognizes that the public void method to which the Test annotation is connected can be used as a test case. JUnit creates a new instance of the class before invoking the annotated method to run the method. JUnit will indicate any exceptions thrown by the test as a failure.
     
  4. What is @ValueSource in JUnit?
    @ValueSource is an ArgumentsSource which provides access to an array of literal values.
     
  5. How can we pass empty or null values into the test?
    We can pass empty or null values into the test via @EmptySource, @NullSource or @NullAndEmptySource

 

Read Also -  Difference between argument and parameter

Key Takeaways

From this article, we learn dependency Injection for constructors and Methods in Junit5 using ParameterResolver. 
But this is not enough; you need something extra to excel in web development truly. If you want to learn more about web development, you can read our articles or take our highly curated Web Development course.

Live masterclass