javadependency-injectionmockitojunit4springmockito

@InjectMocks inject @MockBean by Constructor and setter not working properly


I have try so many time by unsing @RunWith(SpringJUnit4ClassRunner.class) I have tried to create a test case foe a class with getter and Constructor injection. When i user @MockBean for setter injection, @Mock for Constructor injection and also use @RunWith(SpringJUnit4ClassRunner.class) and MockitoAnnotations.initMocks(this); bean injection. If i comment MockitoAnnotations.initMocks(this); constructor injection not working. Now all beans are injected perfectly but @Mock beans(Contructor injected ) beans mocked mthods not working properly when its called.

@Component
Class A{
}

@Component
Class B {
}

@Component
Class c{
}

@Component
Class D{
@Atowired
A a;

B b;
C c;
@Autowired
public D(B b,C c){
b=b;
c=c;
}
}

My Test Class is

@RunWith(SpringJUnit4ClassRunner.class)
Class TestClass{
@MockBean
A mockA
@Mock
B mockB
@Mock
C mockC
@InjectMocks
D mockD

@Before
public void setUp() {
MockitoAnnotations.initMocks(this);//Without this Constructor injection not working
when(mockA.getValue()).then("StringValA");
when(mockB.getValue()).then("StringValB");
when(mockC.getValue()).then("StringValC");

}
@Test
public void testMethod(){
mock.getAllValues();// It will call all injested bean method we are mocked in @before 
}
}

The injections are working properly ,issue is belongs to mocked methods of beans which i use @Mock is not working properly means mockB.getValue() and mockC.getValue() retun null but mockA.getValue() return correctly when i test run.


Solution

  • Solution :

    @RunWith(SpringJUnit4ClassRunner.class)
    Class TestClass {
        @MockBean
        A mockA
    
        @MockBean
        B mockB
    
        @MockBean
        C mockC
    
        @Autowired
        D mockD
    
        @Before
        public void setUp() {
            mockD = new D(mockA,mockB);
            MockitoAnnotations.initMocks(this);
            when(mockA.getValue()).then("StringValA");
            when(mockB.getValue()).then("StringValB");
            when(mockC.getValue()).then("StringValC");
        }
    
        @Test
        public void testMethod(){
            mock.getAllValues(); // It will call all injested bean method we are mocked in @before 
        }
    }
    

    Normal bean initialize methods like SpringJUnit4ClassRunner or MockitoAnnotations.initMocks(this); handling only one type injection at a time. Either constructor or Auto wired injection.

    @RunWith(SpringJUnit4ClassRunner.class) means public class SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner. SpringJUnit4ClassRunner is a custom extension of JUnit's. It will initialize mock the @MockedBean and @Bean annottated beans at the initial time of test run. Also, running the bean injection also.

    MockitoAnnotations.initMocks(this) method has to called to initialize annotated fields. In above example, initMocks() is called in @Before (JUnit4) method of test's base class. For JUnit3 initMocks() can go to setup() method of a base class.

    So that in above question you have used the SpringJUnit4ClassRunner and MockitoAnnotations.initMocks(this) it will create two mock bean references for which ever you use the @Mock. Also in above code flow.

    1. At the beginning the SpringJUnit4ClassRunner run it will create bean reference for @Mock and @MockBean annotated attributes.after that it will create injected bean at tjis time only happens the constructor injection

    2. Run @Before and Run MockitoAnnotations.initMocks(this); it will create another mock references for @Mock annotated attributes and replaced direct reference only. and Auto wired injection run at this time.

    3. After running the MockitoAnnotations.initMocks(this); you will see the all beans are initialized and injected well. But beans which are injected through the constructor those beans are not correct reference these are referring old bean reference which are created by SpringJUnit4ClassRunner.

    If you want to achieve the constructor and Auto wired injection for a single bean you must to use manual bean injected for the constructor injection. You can refer here.