Search code examples
javaspring-bootjunit5

Why Junit fails to perform dependency injection in test?


I have primitive Spring-Boot project with Junit5 and Lombok. I have a simple class:

public class Calculator {
    public int subtract(int a, int b) {
        return a - b;
    }
}

And created test for it:

import lombok.AllArgsConstructor;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

@AllArgsConstructor
class CalculatorTest {
    private final Calculator calculator;

    private static final int SUBTRACTION_RESULT = 2;


    @Test
    void subtractTest() {
        int result = calculator.subtract(5, 3);
        assertEquals(result, SUBTRACTION_RESULT);
    }
}

Test fail with:

org.junit.jupiter.api.extension.ParameterResolutionException: No ParameterResolver registered for parameter [final com.udemy.junits.Calculator calculator] in constructor [public com.udemy.junits.CalculatorTest(com.udemy.junits.Calculator)].

As I understand it fails to initialize and instantiate Calculator. I tried to do this without Lombok and added constructor:

public CalculatorTest(Calculator calculator) {
    this.calculator = calculator;
}

Had same result. Then I instantiated this in this way using @BeforeAll:

@BeforeAll
    static void init() {
        calculator = new Calculator();
    }

This worked. Dependency injection is not available here, because we don't have app context up? Or I understand wrong? Is it possible to perform dependency injection here without instantiating calculator var as new Calculator() ?


Solution

  • Your final code block is correct and is the standard practice, you don't need a constructor in this test class for the Calculator instance.

    For the test class you should define the fields and initialize them in a @BeforeAll or @BeforeEach method.

    Using a constructor for a test class in JUnit is reserved for parameterized unit tests, which is why you are getting an error stating that you're missing a ParameterResolver. This would be an example where you would use a constructor for a parameterized test:

    public class Calculator {
        public int subtract(int a, int b) {
            return a - b;
        }
    }
    
    @RequiredArgsConstructor
    @RunWith(Parameterized.class)
    class CalculatorTest {
        private final int x;
        private final int y;
        private final int z;
        private Calculator calculator;
     
        // Constructor is generated by lombok with @RequiredArgsConstructor
        // and accepts three parameters, x, y, and z
    
        @BeforeEach
        void setUp() {
            calculator = new Calculator();
        }
    
        @Test
        void testSubtract() {
            assertEquals(z, calculator.subtract(x, y));
        }
        
        /**
         * This method will pass each parameter into the constructor
         * of this test class. In this case, the testSubtract method
         * will be ran 4 times with each set of parameters.
         */
        @Parameterized.Parameters
        static Collection parameters() {
            return Arrays.asList(
                new Object[][] {
                   {5, 3, 2},
                   {10, 1, 9},
                   {120, 40, 80},
                   {1, 1, 0}
                }
            );
        }
    }
    

    You can find additional details about Parameterized Unit Testing here for JUnit 4 and here for JUnit 5.