Search code examples
javaspring-bootjunitspring-test

Generating unit tests for my service implementations on the spring boot application


after hours of tries and fails I come to you in hope of a solution. I'm struggle making unit tests for my spring boot application. I'm using mockito and Junit 5.

My architecture is made out like this:

  • A controller
  • An interface of the service
  • A implementation of the service interface
  • A repository extending CrudRepository<Entity, Long>

For now I just want to test out my service implementation.

This is how it looks like for now :

`

@SpringBootTest public class ServiceImplTest{
        @Mock    
     private Entity e;


     @MockBean
     private EntityRepository entityRepository;
        
     @MockBean
     private EntityService entityService;
    
     @BeforeEach
         init(){
               e = new Entity();
               e.name ="abc";
          }
    
    
    
    @Test
     private simpleTest(){
        // saving my element in the mocked repository
        entityRepository.save(e);
    
    
        // I have a repository query to delete an element in a specific way. I ask it to return 1 if it receives the order to activate this method
        doReturn(1).when(entityRepository).specialDeleteEntity(1L);
    
    
       // in the code serviceDeleteEntity() does some operations then calls entityRepository.specialDeleteEntity
        int howMany = entityService.serviceDeleteEntity(1L);
    
    
         // this fails because there was nothing in the repository to be deleted 
         assertEquals(howMany, 1);
    
     }
}

I just have a feeling the the Mocked Repository is not connected to my Mocked Service and by this, the operations between them don't work.

I have also tried another solution where I didn't mock the repository , just in case :

@SpringBootTest class ServiceImplTest {
    @MockBean
    private EntityRepository mockEntityRepository;
    
    @Autowired
    private EntityService entityService;
    
    
    @Test
    void testDelete() {
        // Given
        final Entity entity = new Entity();
        entity.name = "abc";
    
        // Setup
        when(mockEntityRepository.specialDeleteEntity(1L)).thenReturn(1);
    
        // When
        final int result = entityService.specialDeleteEntity(1L);
    
        // Then
        assertThat(result).isEqualTo(1);
        verify(mockEntityRepository).specialDeleteEntity(1L);
    }
}

I may lack some anotations or some methods maybe. I just want your advice on the problem and maybe a step towards the solution. Thank you very much.


Solution

  • There are a few issues with your test:

    1. Wrong use of mocks

    A mock is a dummy implementation of a class. You use them to bypass the normal behaviour of those classes. For example, if you write a unit test for EntityService, you don't want to set up an entire database and insert/delete mock data. So in that case you want to mock dependencies such as EntityRepository.

    This means that in your EntityServiceTest you should only be mocking EntityRepository. You shouldn't mock Entity because this class normally doesn't contain a lot of behaviour and you shouldn't mock EntityService because you want to test the actual behaviour of this service and not of a mock.

    2. Choose one mocking framework

    Your test is currently using a combination of @Mock and @MockBean. Both of these annotations allow you to mock a class. The difference is that @Mock relies only on Mockito and @MockBean mocks the class and creates a Spring bean of it to use for autowiring.

    This means that the @MockBean annotation only works when you run (a part of) your Spring Boot application. This is why you use the @SpringBootTest annotation. The downside is that this might require additional configuration and slows down the tests due to starting/stopping the application.

    If you only need to unit test a single class with some mocks, it would be easier to write a test with Mockito only. To do so, use the @Mock annotation and replace @SpringBootTest with @ExtendWith(MockitoExtension.class):

    // Replace @SpringBootTest with @ExtendWith to enable testing with Mockito
    @ExtendWith(MockitoExtension.class)
    class EntityServiceTest {
        // Add @InjectMocks to the class where the mocks should be injected into.
        // Normally this is the class that you want to test
        @InjectMocks
        private EntityService service;
        // Use @Mock in stead of @MockBean
        @Mock
        private EntityRepository repository;
    
        // ...
    }
    

    3. Test the behaviour of your class

    As I mentioned before, a mock is a dummy implementation of a class. So that means that if you call repository.save(..), it doesn't really do anything as there's no behaviour and no database behind it.

    What you actually want to test is whether the service.serviceDeleteEntity() method calls the repository.specialDeleteEntity() method with the right arguments.

    This means that your second example of your test is the right way. The only thing you don't need is the entity because your test doesn't rely on it (assuming that your service passes the id argument to the repository and returns the result of the query):

    @Test
    void testDelete() {
        // Given
        when(mockEntityRepository.specialDeleteEntity(1L)).thenReturn(1);
        
        // When
        final int result = entityService.specialDeleteEntity(1L);
        
        // Then
        assertThat(result).isEqualTo(1);
        verify(mockEntityRepository).specialDeleteEntity(1L);
    }