Search code examples
javaspringmockitojunit4lombok

Mockito when().thenReturn() does not behave as expected when lombok @RequiredArgsConstructor(onConstructor = @__(@Autowired)) is used


I have Spring class say

@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
class MainServiceImpl implements MainService {
    private final InternalService internalService;

    public Set<String> do(String anything) {
      Set<String> relevent = internalService.finaIntern(anything);
      return relevent;
    }
}

I am writing Unit Test case as below

@RunWith(MockitoJUnitRunner.class)
class TestMainServiceImpl {

    @InjectMocks
    private MainServiceImpl service;

    @Mock
    InternalService internalService;   

    @Before
    public void init(){
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testDo() { 
        Set<String> setData = new HashSet<>();
        setData.add("ABC");
        String a ="a";
        when(internalService.finaIntern(any(String.class))
                                  .thenReturn(setData);
        Set<String> result = service.do(a);
        assertTrue(!result.isEmpty());
    }

}

Here my testcase fails , but if I remove final form MainServiceImpl and do an explicit @Autowired like below

@Component
class MainServiceImpl implements MainService {
    @Autowired
    private InternalService internalService;
     .....

Here I am curious to know 1. How does my test case pass if I remove final keyword 2. Is it a good practice to use @RequiredArgsConstructor , if yes then how and if no then also why ?

Thanks in advance


Solution

  • It has nothing to do with lombok nor Spring @Autowired The combination of @RunWith(MockitoJUnitRunner.class) and MockitoAnnotations.initMocks(this); is the problem. Removing any of it and the behavior is as expected. You don't need both of them. In fact MockitoAnnotations.initMocks(this); exists only for the cases when you cannot use @RunWith(MockitoJUnitRunner.class), for example if you need to use SpringRunner.class.

    Here is why it doesn't work. First of all of your objects are instantiated. So both your @Mock is created and injected into you @InjectMock object:

    Below you can see, that the new create mock (mocks[0]), service inside of injectInto and mock are the same object.

    enter image description here

    But then the initialization happens the second time. So mockito create a new @Mock object, and the tries to inject it into your @InjectMock object, which is already instantiated. But failed to inject it to the field as long as it's final. So here is what we have after the second initialization:

    enter image description here As you can see, now the mock object inside of your testClassInstance and the mock injected to your Object under test are different.

    What about @RequiredArgsConstructor: for me it's totally ok to use it in the way you did.