Spring boot mockito mock method according to input parameters

Solution 1:

When testing with mocks you should make clear wich class is under test and which other classes are just dependencies that should be mocked. In your case you want to test ServiceFacade on method getMyObjectsLogByExternalCode().

So please keep the whole ServiceFacade object as autowired Spring bean, don't use spys or try to mock parts of it.

Please also note, that you cannot mock methods of autowired services, nor you can trick it with a spy. A spy is actually a facade that can only redirect the initial method call. Internal method calls always refer to the real object.

But as you already did, it makes sense to mock the objectRepository, as it should be ouside of your unit test scope. In your code I can see two methods of this object called:

  objectRepository.findobjectCodesByExternalCode(externalCode); // called once to get the list
  objectRepository.findByObjectCode(objectCode); // called twice, which each element of the list

You should both mock them with standard Mockito style:

  List<String> mockObCodes = Arrays.asList("obcode1", "obcode2");
  when(objectRepository.findObjectCodesByexternalCode(any())).thenReturn(mockObCodes );

  List<OperationLogDTO> mockLogsOb1 = Arrays.asList( new Log(), new Log() );  //Size 2 for ob1
  when(objectRepository.findByObjectCode("obcode1")).thenReturn( mockLogsOb1 );

  List<OperationLogDTO> mockLogsOb2 = Collections.singletonList( new Log() ); //Size 1 for ob1
  when(objectRepository.findByObjectCode("obcode2")).thenReturn( mockLogsOb2 );

I hope this will make your test run, please let me know,

Solution 2:

Firstly - try to mock only collaborators, not your object under test.

Having said that - a Spy can be used to mock parts of object under test.

I don't really see the need to use @SpringBootTest for your service, so here goes plain Mockito version:

@ExtendWith(MockitoExtension.class)
class ObjectServiceFacadeImplTest {

    @Mock
    ObjectRepository repository;

    @Spy
    @InjectMocks
    private ServiceFacadeImpl serviceFacade;

    @Test
    void testGetMyObjectsLogByExternalCode() {

        // Given
        List<String> mockObCodes = List.of("obcode1", "obcode2");
        String externalCode = "TESTCODE";
        when(repository.findObjectCodesByExternalCode(externalCode)).thenReturn(mockObCodes);

        List<Log> mockLogsOb1 = List.of(new Log(), new Log());  //Size 2 for ob1
        when(serviceFacade.getObjectLog("obcode1")).thenReturn(mockLogsOb1);

        List<Log> mockLogsOb2 = List.of(new Log()); //Size 1 for ob1
        when(serviceFacade.getObjectLog("obcode2")).thenReturn(mockLogsOb2);

        // When
        MyObjectWrapper objectWrapper = serviceFacade.getMyObjectsLogByExternalCode(externalCode);

        // Then
        assertEquals(objectWrapper.getExternalCode(), externalCode);
        assertEquals(objectWrapper.getObjects().size(), 2);
        assertEquals(objectWrapper.getObjects().get(0).getLogs().size(), 2);  //Fails because it gets the second mock return value
        assertEquals(objectWrapper.getObjects().get(0).getCode(), "obcode1"); //Fails because it gets the second mock return value
        assertEquals(objectWrapper.getObjects().get(1).getLogs().size(), 1);
        assertEquals(objectWrapper.getObjects().get(1).getCode(), "obcode2");
    }
}