I am encountering an intermittent issue with my unit tests for a Spring service where the method under test occasionally returns null. This issue is random and affects different methods during the Act phase of the tests.
package fr.hoenheimsports.trainingservice.services;
import fr.hoenheimsports.trainingservice.Exception.CoachNotFoundException;
import fr.hoenheimsports.trainingservice.assemblers.ModelAssembler;
import fr.hoenheimsports.trainingservice.dto.CoachDto;
import fr.hoenheimsports.trainingservice.mappers.CoachMapper;
import fr.hoenheimsports.trainingservice.models.Coach;
import fr.hoenheimsports.trainingservice.repositories.CoachRepository;
import fr.hoenheimsports.trainingservice.ressources.CoachModel;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.hateoas.PagedModel;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
@Service
public class CoachServiceImpl implements CoachService {
private final CoachRepository coachRepository;
private final ModelAssembler<CoachDto, CoachModel> coachModelAssembler;
private final ModelAssembler<Page<CoachModel>, PagedModel<CoachModel>> coachPagedModelAssembler;
private final CoachMapper coachMapper;
private final SortUtil sortUtil;
public CoachServiceImpl(CoachRepository coachRepository, ModelAssembler<CoachDto, CoachModel> coachModelAssembler, ModelAssembler<Page<CoachModel>, PagedModel<CoachModel>> coachPagedModelAssembler, CoachMapper coachMapper, SortUtil sortUtil) {
this.coachRepository = coachRepository;
this.coachModelAssembler = coachModelAssembler;
this.coachPagedModelAssembler = coachPagedModelAssembler;
this.coachMapper = coachMapper;
this.sortUtil = sortUtil;
}
@Override
public CoachModel createCoach(CoachDto coachDto) {
Coach savedCoach = coachRepository.save(Coach.builder()
.email(coachDto.email())
.name(coachDto.name())
.phone(coachDto.phone())
.surname(coachDto.surname())
.build());
return coachModelAssembler.toModel(coachMapper.toDto(savedCoach));
}
@Override
public PagedModel<CoachModel> getAllCoaches(int page, int size, List<String> sort) {
Pageable pageable = PageRequest.of(page, size, this.sortUtil.createSort(sort));
return coachPagedModelAssembler.toModel(coachRepository.findAll(pageable).map(coachMapper::toDto).map(coachModelAssembler::toModel));
}
@Override
public CoachModel getCoachById(Long id) throws CoachNotFoundException {
return coachRepository.findById(id)
.map(coachMapper::toDto)
.map(coachModelAssembler::toModel)
.orElseThrow(CoachNotFoundException::new);
}
@Override
public CoachModel updateCoach(Long id, CoachDto coachDto) throws CoachNotFoundException {
Coach coachToUpdate = coachRepository.findById(id).orElseThrow(CoachNotFoundException::new);
coachToUpdate.setEmail(coachDto.email());
coachToUpdate.setName(coachDto.name());
coachToUpdate.setPhone(coachDto.phone());
coachToUpdate.setSurname(coachDto.surname());
Coach savedCoach = coachRepository.save(coachToUpdate);
return coachModelAssembler.toModel(coachMapper.toDto(savedCoach));
}
@Override
public void deleteCoach(Long id) {
coachRepository.deleteById(id);
}
}
package fr.hoenheimsports.trainingservice.services;
import fr.hoenheimsports.trainingservice.Exception.CoachNotFoundException;
import fr.hoenheimsports.trainingservice.assemblers.ModelAssembler;
import fr.hoenheimsports.trainingservice.dto.CoachDto;
import fr.hoenheimsports.trainingservice.mappers.CoachMapper;
import fr.hoenheimsports.trainingservice.models.Coach;
import fr.hoenheimsports.trainingservice.repositories.CoachRepository;
import fr.hoenheimsports.trainingservice.ressources.CoachModel;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.domain.*;
import org.springframework.hateoas.PagedModel;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.reset;
@ExtendWith(MockitoExtension.class)
public class CoachServiceTest {
@Mock
private CoachRepository coachRepository;
@Mock
private ModelAssembler<CoachDto, CoachModel> coachModelAssembler;
@Mock
private ModelAssembler<Page<CoachModel>, PagedModel<CoachModel>> coachPagedModelAssembler;
@Mock
private CoachMapper coachMapper;
@Mock
private SortUtil sortUtil;
@InjectMocks
private CoachServiceImpl coachService;
@BeforeEach
public void setUp() {
reset(coachRepository, coachModelAssembler, coachPagedModelAssembler, coachMapper, sortUtil);
}
@AfterEach
public void tearDown() {
reset(coachRepository, coachModelAssembler, coachPagedModelAssembler, coachMapper, sortUtil);
}
@Test
public void testCreateCoach() {
// Arrange
CoachDto coachDto = new CoachDto(null, "John", "Doe", "[email protected]", "1234567890");
Coach coach = Coach.builder()
.name("John")
.surname("Doe")
.email("[email protected]")
.phone("1234567890")
.build();
Coach savedCoach = Coach.builder()
.id(1L)
.name("John")
.surname("Doe")
.email("[email protected]")
.phone("1234567890")
.build();
CoachModel coachModel = new CoachModel(new CoachDto(1L, "John", "Doe", "[email protected]", "1234567890"));
given(coachRepository.save(any(Coach.class))).willReturn(savedCoach);
given(coachMapper.toDto(savedCoach)).willReturn(new CoachDto(1L, "John", "Doe", "[email protected]", "1234567890"));
given(coachModelAssembler.toModel(any(CoachDto.class))).willReturn(coachModel);
// Act
CoachModel createdCoach = coachService.createCoach(coachDto);
// Debugging logs
System.out.println("Created Coach ID: " + createdCoach.getContent().id());
// Assert
assertThat(createdCoach).isNotNull();
assertThat(Objects.requireNonNull(createdCoach.getContent()).id()).isEqualTo(1L);
then(coachRepository).should(times(1)).save(any(Coach.class));
}
@Test
public void testGetAllCoaches() {
// Arrange
List<String> sortParams = List.of("name,asc");
Pageable pageable = PageRequest.of(0, 10, Sort.by("name").ascending());
List<Coach> coaches = Arrays.asList(
Coach.builder()
.id(1L)
.name("John")
.surname("Doe")
.email("[email protected]")
.phone("1234567890")
.build(),
Coach.builder()
.id(2L)
.name("Jane")
.surname("Doe")
.email("[email protected]")
.phone("0987654321")
.build()
);
Page<Coach> coachPage = new PageImpl<>(coaches, pageable, coaches.size());
List<CoachDto> coachDtos = Arrays.asList(
new CoachDto(1L, "John", "Doe", "[email protected]", "1234567890"),
new CoachDto(2L, "Jane", "Doe", "[email protected]", "0987654321")
);
List<CoachModel> coachModels = Arrays.asList(
new CoachModel(coachDtos.get(0)),
new CoachModel(coachDtos.get(1))
);
PagedModel<CoachModel> pagedModel = PagedModel.of(coachModels, new PagedModel.PageMetadata(10, 0, coachModels.size()));
given(sortUtil.createSort(sortParams)).willReturn(pageable.getSort());
given(coachRepository.findAll(any(Pageable.class))).willReturn(coachPage);
given(coachMapper.toDto(any(Coach.class))).willReturn(coachDtos.get(0), coachDtos.get(1));
given(coachPagedModelAssembler.toModel(ArgumentMatchers.any())).willReturn(pagedModel);
// Act
PagedModel<CoachModel> result = coachService.getAllCoaches(0, 10, sortParams);
// Debugging logs
System.out.println("Result size: " + result.getContent().size());
// Assert
assertThat(result).isNotNull();
assertThat(result.getContent()).hasSize(2).containsExactlyInAnyOrderElementsOf(coachModels);
then(coachRepository).should(times(1)).findAll(any(Pageable.class));
}
@Test
public void testGetCoachById() throws CoachNotFoundException {
// Arrange
Coach coach = Coach.builder()
.id(1L)
.name("John")
.surname("Doe")
.email("[email protected]")
.phone("1234567890")
.build();
CoachDto coachDto = new CoachDto(1L, "John", "Doe", "[email protected]", "1234567890");
CoachModel coachModel = new CoachModel(coachDto);
given(coachRepository.findById(1L)).willReturn(Optional.of(coach));
given(coachMapper.toDto(coach)).willReturn(coachDto);
given(coachModelAssembler.toModel(coachDto)).willReturn(coachModel);
// Act
CoachModel result = coachService.getCoachById(1L);
// Debugging logs
System.out.println("Fetched Coach ID: " + Objects.requireNonNull(result.getContent()).id());
// Assert
assertThat(result).isNotNull();
assertThat(Objects.requireNonNull(result.getContent()).id()).isEqualTo(1L);
then(coachRepository).should(times(1)).findById(1L);
}
@Test
public void testUpdateCoach() throws CoachNotFoundException {
// Arrange
CoachDto coachDto = new CoachDto(null, "John", "Doe", "[email protected]", "1234567890");
Coach coach = Coach.builder()
.id(1L)
.name("John")
.surname("Doe")
.email("[email protected]")
.phone("1234567890")
.build();
Coach updatedCoach = Coach.builder()
.id(1L)
.name("John")
.surname("Doe")
.email("[email protected]")
.phone("1234567890")
.build();
CoachDto updatedCoachDto = new CoachDto(1L, "John", "Doe", "[email protected]", "1234567890");
CoachModel updatedCoachModel = new CoachModel(updatedCoachDto);
given(coachRepository.findById(1L)).willReturn(Optional.of(coach));
given(coachRepository.save(any(Coach.class))).willReturn(updatedCoach);
given(coachMapper.toDto(updatedCoach)).willReturn(updatedCoachDto);
given(coachModelAssembler.toModel(updatedCoachDto)).willReturn(updatedCoachModel);
// Act
CoachModel result = coachService.updateCoach(1L, coachDto);
// Debugging logs
System.out.println("Updated Coach ID: " + Objects.requireNonNull(result.getContent()).id());
// Assert
assertThat(result).isNotNull();
assertThat(Objects.requireNonNull(result.getContent()).id()).isEqualTo(1L);
then(coachRepository).should(times(1)).findById(1L);
then(coachRepository).should(times(1)).save(any(Coach.class));
}
@Test
public void testDeleteCoach() {
// Arrange
willDoNothing().given(coachRepository).deleteById(1L);
// Act
coachService.deleteCoach(1L);
// Assert
then(coachRepository).should(times(1)).deleteById(1L);
}
}
I have tried to isolate the problem by resetting mocks, verifying mock configurations, and ensuring proper test isolation, but the issue persists. Above is the code for my service implementation (CoachServiceImpl) and the unit test class (CoachServiceTest).
I have added logs and ensured the mocks are reset before and after each test. Despite this, the issue of intermittent null values persists.
Has anyone encountered similar issues or have suggestions on how to further debug and resolve this problem?