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:
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.
There are a few issues with your test:
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.
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;
// ...
}
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);
}