Mockito given().willReturn() returns sporadic result - spring

I am testing a simple logic using mockito-all 1.10.19 and spring-boot-starter-parent 2.0.4.RELEASE. I have a service, which determines whether the uploaded file has the same store codes or not. If it has, IllegalArgumentException is been thrown:
public class SomeService {
private final CutoffRepository cutoffRepository;
private final Parser<Cutoff> cutoffParser;
public void saveCutoff(MultipartFile file) throws IOException {
List<Cutoff> cutoffList = cutoffParser.parse(file.getInputStream());
boolean duplicateStoreFlag = cutoffList
.stream()
.collect(Collectors
.groupingBy(Cutoff::getStoreCode, Collectors.counting()))
.values()
.stream()
.anyMatch(quantity -> quantity > 1);
if (duplicateStoreFlag) {
throw new IllegalArgumentException("There are more than one line corresponding to the same store");
}
//Some saving logic is here
}
}
I mock up cutoffParser.parse() so, that it returns ArrayList<CutOff> with two elements within it:
#RunWith(SpringRunner.class)
#SpringBootTest
public class SomeServiceTest {
#Mock
private CutoffRepository cutoffRepository;
#Mock
private Parser<Cutoff> cutoffParser;
#InjectMocks
private SomeService someService;
#Test(expected = IllegalArgumentException.class)
public void saveCutoffCurruptedTest() throws Exception {
Cutoff cutoff1 = new Cutoff();
cutoff1.setStoreCode(1);
Cutoff cutoff2 = new Cutoff();
//corruption is here: the same storeCode
cutoff2.setStoreCode(1);
List<Cutoff> cutoffList = new ArrayList<>();
cutoffList.add(cutoff1);
cutoffList.add(cutoff2);
MockMultipartFile mockMultipartFile = new MockMultipartFile("file.csv", "file".getBytes());
//here what I expect to mock up a response with the list
given(cutoffParser.parse(any())).willReturn(cutoffList);
someService.saveCutoff(mockMultipartFile);
}
}
But the behavior I encounter is sporadic. The test is passed from time to time. During debugging I sometimes get list of size 2, sometimes get list of size 0. What is the reason of such an unpredictable behavior?
I am definitely missing something. Any help is highly appreciated.
P.S. the same situation using IntelliJ Idea and Ubuntu terminal.

Supposedly, the reason is pointed out here https://github.com/mockito/mockito/issues/1066. #InjectMocks and #Mock<...> cause test to fail occasionally.

Related

Mockito how to mock Optional.map().orElseThrow()

today I come across below problem-
I'm doing an Udemy course and I try to test below method:
public GroupReadModel createGroup(LocalDateTime deadline, Integer projectId) {
if (!configurationProperties.getTemplate().isAllowMultipleTasks() && taskGroupRepository.existsByDoneIsFalseAndProject_Id(projectId)) {
throw new IllegalStateException("Only one undone group form project is allowed");
}
TaskGroup result = projectRepository.findById(projectId)
.map(project -> {
TaskGroup taskGroup = new TaskGroup();
taskGroup.setDescription(project.getDescription());
taskGroup.setTasks(project.getProjectSteps().stream()
.map(step -> Task.createNewTask(step.getDescription(), deadline.plusDays(step.getDaysToDeadline())))
.collect(Collectors.toSet()));
taskGroup.setProject(project);
return taskGroupRepository.save(taskGroup);
}).orElseThrow(() -> new NoSuchElementException(String.format("No project with ID %d found", projectId)));
return new GroupReadModel(result);
}
Here is test method:
#ExtendWith(SpringExtension.class)
class ProjectServiceTest {
#Autowired
private ProjectService projectService;
#MockBean
private ProjectRepository projectRepository;
#MockBean
private TaskGroupRepository taskGroupRepository;
#MockBean
private TaskConfigurationProperties configurationProperties;
#Mock
private TaskConfigurationProperties.Template template;
#TestConfiguration
static class ProjectServiceTestConfig {
#Bean
ProjectService projectService(ProjectRepository projectRepository, TaskGroupRepository taskGroupRepository, TaskConfigurationProperties configurationProperties ){
return new ProjectService(projectRepository, taskGroupRepository, configurationProperties);
}
}
#Test
void should_return_new_group_read_model() {
//given
LocalDateTime deadline = LocalDateTime.now();
Integer projectId = 99;
Project projectById = new Project(projectId, "test project");
projectById.setProjectSteps(Set.of(new ProjectStep("test1", 2)));
TaskGroup taskGroupSaved = TaskGroup.CreateNewTaskGroup(projectById.getDescription(), Set.of(Task.createNewTask("test1", LocalDateTime.now())));
GroupReadModel expectedResult = new GroupReadModel(taskGroupSaved);
expectedResult.setDeadline(expectedResult.getDeadline().plusDays(2));
Mockito.when(configurationProperties.getTemplate()).thenReturn(template);
Mockito.when(template.isAllowMultipleTasks()).thenReturn(true);
Mockito.when(taskGroupRepository.existsByDoneIsFalseAndProject_Id(projectId)).thenReturn(false);
Mockito.when(projectRepository.findById(projectId)).thenReturn(Optional.of(projectById));
//when
GroupReadModel result = projectService.createGroup(deadline, projectId);
//then
assertEquals(expectedResult, result);
}
My problem is that
Mockito.when(projectRepository.findById(projectId)).thenReturn(Optional.of(projectById));
resulting java.util.NoSuchElementException: No project with ID 99 found
like it was never mocked. What is interesting to me, this will work for below:
Project projectById = projectRepository.findById(projectId)
.orElseThrow(() -> new NoSuchElementException(String.format("No project with ID %d found", projectId)));
TaskGroup taskGroup = new TaskGroup();
taskGroup.setDescription(projectById.getDescription());
taskGroup.setTasks(projectById.getProjectSteps().stream()
.map(step -> Task.createNewTask(step.getDescription(), deadline.plusDays(step.getDaysToDeadline())))
.collect(Collectors.toSet()));
taskGroup.setProject(projectById);
taskGroupRepository.save(taskGroup);
As you can see, first I'm getting my object from repository, then the rest of logic takes place. However I wonder what do I do wrong so it will not work with mapping
MapResult result = projectRepository.findById(projectId)
.map(some_logic)
.orElseThrow(some_exception)
Please advise what do I do wrong, an how can I correct that?
You returned null from the .map() call, thus you fall into orElseThrow().
null comes from return taskGroupRepository.save(taskGroup);
The repository is a mock, save is not stubbed, thus null is returned.
On top if that:
You dont need to mock everything
If any of your objects are POJOs, construct them with desired state instead of mocking.
Simplify test setup
#SpringBootTest may be an overkill for your test. #MockitoExtension, #Mock and #InjectMocks should be enough.

How to test findById method?

First - I've checked all previous topics around this question and none of them helped.
Having the following code:
#DisplayName("GET RecipeUltraLight by id is successful")
#Test
public void givenRecipeId_whenGetRecipeDetailsById_thenReturnRecipeObject(){
// given
given(this.recipeRepository.findById(recipe.getId())).willReturn(Optional.of(recipe));
given(this.recipeService.getRecipeById(recipe.getId())).willReturn(recipe);
given(this.recipeConverter.toUltraLight(recipe)).willReturn(recipeUltraLightDto);
// when
RecipeUltraLightDto retrievedRecipe = recipeService.getRecipeUltraLightById(recipe.getId());
// then
verify(recipeRepository, times(1)).findById(recipe.getId());
verify(recipeService, times(1)).getRecipeById(recipe.getId());
verify(recipeConverter, times(1)).toUltraLight(recipe);
assertThat(retrievedRecipe).isNotNull();
}
gives me this error:
org.mockito.exceptions.misusing.WrongTypeOfReturnValue:
Recipe cannot be returned by findById()
findById() should return Optional
***
If you're unsure why you're getting above error read on.
Due to the nature of the syntax above problem might occur because:
1. This exception *might* occur in wrongly written multi-threaded tests.
Please refer to Mockito FAQ on limitations of concurrency testing.
2. A spy is stubbed using when(spy.foo()).then() syntax. It is safer to stub spies -
- with doReturn|Throw() family of methods. More in javadocs for Mockito.spy() method.
Service method:
#Transactional(readOnly = true)
public RecipeUltraLightDto getRecipeUltraLightById(Long id) {
Recipe recipe = getRecipeById(id);
RecipeUltraLightDto dto = new RecipeUltraLightDto();
dto = recipeConverter.toUltraLight(recipe);
return dto;
}
// internal use only
#Transactional(readOnly = true)
public Recipe getRecipeById(Long id) {
if (id == null || id < 1) {
return null;
}
return recipeRepository.findById(id)
.orElseThrow(() -> new RecipeNotFoundException(
String.format("Recipe with id %d not found.", id)
));
}
Setup:
#ContextConfiguration(classes = {RecipeService.class})
#ExtendWith({SpringExtension.class, MockitoExtension.class})
class RecipeServiceTest {
#MockBean
private RecipeConverter recipeConverter;
#MockBean
private RecipeRepository recipeRepository;
#Autowired
private RecipeService recipeService;
private Recipe recipe;
private RecipeUltraLightDto recipeUltraLightDto;
#BeforeEach
public void setup(){
recipe = Recipe.builder()
.id(1L)
.name("Recipe")
.description("Description")
.createdAt(LocalDateTime.now())
.difficulty(RecipeDifficulty.EASY)
.minutesRequired(60)
.portions(4)
.authorId(1L)
.views(0)
.isVerified(false)
.build();
recipeUltraLightDto = RecipeUltraLightDto.builder()
.id(1L)
.name("Recipe")
.build();
}
I've tried:
Optinal.ofNullable()
Adding .isPresent()
Getting rid of .orElseThrow and going through if statements and using .get()
Kotlin
Will be glad if someone can help.
You are creating a mock of the object you are testing and with that basically also render the mocking of the repository useless.
You should remove the line given(this.recipeService.getRecipeById(recipe.getId())).willReturn(recipe); that way it will just call the method and call the repository. Which now will return the mocked result. As that is the behavior that will now kick in.
It is clearly mentioned that the method findById() returning Optional, you need to get Recipe by invoking Optional.get().

Step is hang during the read() method

I have a job and in one of the steps, the reader needs to handle a list of 6000 +/- objects. This is not a big volume of data and it's not the first time that I am working with this size of a volume.
For some reason, the reader can hang for an hour or even more after each chunk in the read() function. I don’t know what is wrong and have no idea how to debug this step.
#Autowired
private ModelMapper modelMapper;
#Value("${chunk.size}") //500
private int chunkSize;
#javax.annotation.Resource
private Environment environment;
private ItemReader<Device> delegate;
public void setDelegate(ItemReader<Device> delegate) {
this.delegate = delegate;
}
#Override
public void beforeStep(StepExecution stepExecution) {
List<AccountDeviceDto> outboundList = (List<AccountDeviceDto>) stepExecution.getJobExecution().getExecutionContext().get("outboundThatMightSwappedList");
List <DeviceDto> accountList = outboundList .stream().map(dto -> dto.getDevice()).collect(Collectors.toList());
Type listType = new TypeToken<List<Device>>() {}.getType();
List<Device> mapList = modelMapper.map(accountList,listType);
setDelegate(new IteratorItemReader<Device>(mapList));
}
#Override
public ExitStatus afterStep(StepExecution stepExecution) {
logger.info("** start afterStep **");
return ExitStatus.COMPLETED;
}
#Override
public Device read() throws Exception, UnexpectedInputException, ParseException, NonTransientResourceException {
return delegate.read();
}
#Data
#Entity
#Table(name = "Device")
public class Device {
#Id
#Column(name = "MacAddress")
private String macAddress;
......
.....
....
...
..
#Column(name = "LastModifiedDate")
private LocalDate lastModifiedDate;
#OneToMany(mappedBy = "device")
private List<AccountDevice> accountDeviceList;
}
Any idea how this can be debugged to figure what is the bottleneck.
Thank you
First thing you need to do is to find out which line of the code does it hang at and then troubleshoot from there.
Firstly find out the process ID of your spring-batch app by :
jps -v
Assuming the process ID is 1234 , then use jstack to print out the stack trace of the java thread for this process. You could then see the related threads are hanging at which lines of the codes :
jstack 1234
You seem to be putting the list of items in the execution context in a previous step and then reading them from the execution context with a delegate IteratorItemReader.
The execution context is persisted between steps, and what's happening is that your big items list is taking time to be deserialized before even starting to read the first item. I'm pretty sure this is the cause of your issue, but you can debug/time the beforeStep method to confirm that.

Spring Boot Testing with Mockito and ExpectedException - console printing 'null' is normal?

I'm writing some test classes for my services and noticed a weird behavior that happens after test results are returned. The console prints 'null' messages and no other information about tests. The tests work fine as I've tried to fail them to ensure that is the case. Is this the expected behavior?
#RunWith(MockitoJUnitRunner.class)
public class GradeServiceTest {
#Rule
public ExpectedException thrown = ExpectedException.none();
#Mock
private GradeRepository gradeRepository;
#Mock
private AssignmentService assignmentService;
#InjectMocks
private GradeService gradeService;
private Assignment assignment;
private Grade grade;
#Before
public void init() {
assignment = new Assignment.Builder()
.withId(0L)
.withModuleId(0L)
.withName("Test assignment")
.withWeightEnabled(false)
.withWeight(0)
.build();
grade = new Grade.Builder()
.withId(0L)
.withAssignmentId(0L)
.withGradeType(GradeType.PERCENTAGE)
.withGradeValue("50%")
.build();
}
#Test
public void shouldAddGrade() throws AssignmentException, GradeException {
// GIVEN
when(assignmentService.exists(grade.getAssignmentId())).thenReturn(true);
when(gradeRepository.insert(any())).thenReturn(grade);
// WHEN
Grade addedGrade = gradeService.addGrade(grade.getAssignmentId(), grade.getType().name(), grade.getValue());
// THEN
assertThat(addedGrade).isEqualTo(grade);
}
#Test
public void shouldNotAddGradeIfAssignmentDoesNotExist() throws AssignmentException, GradeException {
// GIVEN
when(assignmentService.exists(grade.getAssignmentId())).thenReturn(false);
// EXPECT
thrown.expect(AssignmentException.class);
thrown.expectMessage(AssignmentErrorMessages.NOT_FOUND);
// THEN
gradeService.addGrade(grade.getAssignmentId(), grade.getType().name(), grade.getValue());
}
}
I don't think this is normal behavior for each test to be printing 'null' without any other information. Could someone help me understand what is wrong with the code?
Test results:
null
null
Process finished with exit code 0

#WebMvcTest content is null

I've already read this Q&A but it didn't solve the problem. I'm using Spring Boot 1.4.2.RELEASE and I'm attempting to speed up my tests. Up to this point, I've used #SpringBootTest and I'm testing switching some of these simpler tests to #WebMvcTest.
My controller has the following method which is responding to GET requests.
public ResponseEntity<MappingJacksonValue> fetchOne(#PathVariable Long id, #RequestParam(value = "view", defaultValue = "summary", required = false) String view) throws NotFoundException {
Brand brand = this.brandService.findById(id);
if (brand == null) {
throw new NotFoundException("Brand Not Found");
}
MappingJacksonValue mappingJacksonValue = jsonView(view, brand);
return new ResponseEntity<>(mappingJacksonValue, HttpStatus.OK);
}
My test looks like this:
#RunWith(SpringRunner.class)
#WebMvcTest(BrandController.class)
public class BrandSimpleControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private BrandService brandService;
#Test
public void testExample() throws Exception {
Brand brand = new Brand(1l);
brand.setName("Test Name");
brand.setDateCreated(new Date());
brand.setLastUpdated(new Date());
when(this.brandService.findById(1l)).thenReturn(brand);
this.mockMvc.perform(get("/api/brands/1").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name", is("Test Name")));
}
}
When I run this test, I get nothing back in the content. I'm not doing anything significantly different than this guide, so not sure what I'm missing.
I should note that using #SpringBootTest with the exact same controller works as expected.

Resources