mockMvc jUnit testing with #SortDefault and #PageableDefault - spring

I am trying to test a pagination method which also sorts the content by created date. The method does work how I expect it, however I an unsure how I can correctly test it. Have found no working solution for my problem even the
I know that the issue is caused by #SortDefault being present as when I remove it the problem is solved, could work around it by creating an sql repository call which will sort it asc or desc.
Furthermore the error I currently receive is java.lang.AssertionError: Content type not set
tried adding .setCustomArgumentResolvers(new SortHandlerMethodArgumentResolver()).build(); but without any luck also tried other methods of .andExpect(content().contentTypeCompatibleWith("application/json")) also no luck. Still complains that content type is not set.
Below you can see my controller method
#GetMapping("/stuff")
public ResponseEntity<Page<Stuff>> findAllStuffs(#PageableDefault(value = 20) #SortDefault(sort = "created", direction = Sort.Direction.DESC)Pageable pageable){
log.info("Start of findAllStuffs method");
Page<Stuff> thePage=repository.findAll(pageable);
if(thePage==null )
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
else
return new ResponseEntity<>(thePage,HttpStatus.OK);
}
Below you can see my test controller method
#Test
public void findAllStuffs() throws Exception {
PageRequest pageRequest = new PageRequest(0,20);
Page<Stuff> pages = new PageImpl<>(stuffs);
this.mockMvc = MockMvcBuilders.standaloneSetup(controller).setCustomArgumentResolvers(new SortHandlerMethodArgumentResolver()).build();
when(repository.findAll(pageRequest)).thenReturn(pages);
this.mockMvc.perform(get("/api/stuffs")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(jsonPath("$.content").isArray())
.andExpect(jsonPath("$.content", Matchers.hasSize(4)))
.andExpect(status().isOk());
}
Any help or suggestions will be truly appreciated.

Related

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().

How to mock controller parameter method with Mockito?

I have the following controller method where criterias is an object build with query parameters :
#GetMapping
public List<Employee> findAll(CustomCriteria criterias) {
// this method build a custom mongoDB query object
final Query query = criterias.buildMongoDBQueryFromCriteria();
return employeeService.find(query);
}
The test is written as follow :
#Test
void get_all_employees_with_criterias() {
given(employeeService.find(any(Query.class)))
.willReturn(List.of(new Employee(), new Employee));
final var responseBody = mvc.perform(get("/employees?companyId=12,14")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn()
.getResponse().getContentAsString();
assertThatJson(responseBody).isArray().size().isEqualTo(2);
}
I can't find how to mock criterias.buildMongoDBQueryFromCriteria(). Indeed, there are a lot of logic inside this method, and I don't want it to be called for real with #WebMvcTest.
I have already tried to use #Spy annotation in the controller test class but it doesn't seems to work :/
I'm pretty sure that it must be really basic, but I didn't find any equivalent needs over Google.
Thanks for your help
EDIT
Based on #nnhthuan response I updated my test as follow, but it still doesn't work:
#Test
void get_all_employees_with_criterias() {
var customCriteriaMock = Mockito.mock(CustomCriteria.class);
var mockedQuery = Mockito.mock(Query.class);
when(customCriteriaMock.buildMongoDBQueryFromCriteria()).thenReturn(mockedQuery);
given(employeeService.find(mockedQuery))
.willReturn(List.of(new Employee(), new Employee()));
final var responseBody = mvc.perform(get("/employees?companyId=12,14")
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn()
.getResponse().getContentAsString();
assertThatJson(responseBody).isArray().size().isEqualTo(2);
}
If you're asking how to write unit test, and this is how, not what you're trying to write above.
#Test
public void testFindAll() {
CustomCriteria mockCriteria = Mockito.mock(CustomCriteria.class);
Query mockQuery = Mockito.mock(Query.class);
List<Employee> expectation = new ArrayList<>();
when(mockCriteria.buildMongoDBQueryFromCriteria()).thenReturn(mockQuery);
when(employeeService.find(mockQuery)).thenReturn(expectaion)
List<Employee> actual = controller.findAll(mockCriteria);
assertThat(actual).sameInstance(expectation); // Or whatever framework you are using to assert.
}
If you're asking how to write integration test with your real service, so do not use mocking framework. :)

Mockito when any java.lang.NullPointerException

I have this method which must be tested:
private void processRequest() {
BulkRequest request = new BulkRequest();
request.add(new IndexRequest("posts").id("1")
.source(XContentType.JSON,"field", "foo"));
request.add(new IndexRequest("posts").id("2")
.source(XContentType.JSON,"field", "bar"));
final BulkResponse bulkResponse = restHighLevelClient.bulk(request, RequestOptions.DEFAULT);
}
This is what I'm trying to do from my test class:
RestHighLevelClient restHighLevelClientMock = mock(RestHighLevelClient.class);
final String errorMessage = "error message";
final Exception cause = new Exception("test exception");
final boolean isFailed = true;
final int itemID = 0;
// define the item failure
BulkItemResponse.Failure failure = mock(BulkItemResponse.Failure.class);
when(failure.getCause()).thenReturn(cause);
when(failure.getMessage()).thenReturn(errorMessage);
// define the item level response
BulkItemResponse itemResponse = mock(BulkItemResponse.class);
when(itemResponse.isFailed()).thenReturn(isFailed);
when(itemResponse.getItemId()).thenReturn(itemID);
when(itemResponse.getFailure()).thenReturn(failure);
when(itemResponse.getFailureMessage()).thenReturn("error message");
List<BulkItemResponse> itemsResponses = Collections.singletonList(itemResponse);
// define the bulk response to indicate failure
BulkResponse response = mock(BulkResponse.class);
when(response.iterator()).thenReturn(itemsResponses.iterator());
when(response.hasFailures()).thenReturn(isFailed);
// have the client return the mock response
when(restHighLevelClientMock.bulk(any(BulkRequest.class), RequestOptions.DEFAULT)).thenReturn(response);
I'm getting java.lang.NullPointerException in this line:
when(restHighLevelClientMock.bulk(any(BulkRequest.class), RequestOptions.DEFAULT)).thenReturn(response);
Any idea why this happens? Thanks
I ran into this problem too, which led me to this github request:
https://github.com/elastic/elasticsearch/issues/40534
Elasticsearc's RestHighLevelClient class marked many of the public methods as final, making it impossible to mock.
There is a workaround detailed in the github page about creating a delegate, which is less than ideal but works.
EDIT: after digging around with possible solutions I found this article: see https://www.baeldung.com/mockito-final. I tried it in my own project and got my tests working with junit jupiter.
Add the following to your src/test/resources folder:
mockito-extensions/org.mockito.plugins.MockMaker
add the following line to the org.mockito.plugins.MockMaker file:
mock-maker-inline

Mockito given().willReturn() returns sporadic result

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.

Spring Boot Exception details

I am trying to log the exception in the spring boot based web service.
So I have used GlobalExceptionHandler
My code :
#ControllerAdvice
#RestController
public class GlobalExceptionHandler {
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(value = Exception.class)
public String handleException(Exception e){
System.out.println("Ankit == "+e.getMessage());
StringWriter errors = new StringWriter();
e.printStackTrace(new PrintWriter(errors));
System.out.println(errors.toString());
return e.getMessage();
}
}
the code is working fine. What I want is exception details. I mean the code where the exception occurs? File name / line ? or do I have to parse the stacktrace? I mean spring boot must have thought something for this?
Using IDE
If you are using any IDE then go to Console Window.
Clear console
Repeat action that causes Exception
Search in Console (CTRL + F) for ERROR
Look for line above(Look for 2-3 lines if you don't find immediate above) the line which contains ERROR. This line has details of Class, Method where Exception has occurred.
Without looking at Console or Logs
If you want to use it in production then, handling atleast known exceptions(like BAD_REQUEST, NOT_FOUND etc.) the way it is done below might be helpful (adding an extra parameter to Exception Class) :
Employee employee = employeeService.getEmployeeById(employeeId);
if (null == employee) {
logger.error("No tenant exists for employeeId:"+employeeId);
throw new ObjectNotFoundException("Emplyee Not Found", this.getClass().getSimpleName();));
}
here this.getClass().getSimpleName(); will be passed as parameter from EmployeeController class. So in ObjectNotFoundException we can add a parameter ClassName and When you handle it in GlobalExceptionHandler, you can do it as it is done below,
#ControllerAdvice
#RestController
public class GlobalExceptionHandler {
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(value = Exception.class)
public String handleException(Exception e){
System.out.println("Ankit == "+e.getMessage());
StringWriter errors = new StringWriter();
e.printStackTrace(new PrintWriter(errors));
String classWithExceptionName = e.getClassName();
// you need to add this above getter method to your Exception Class
System.out.println(errors.toString());
return e.getMessage();
}
}
This is for known common exceptions. We need to add extra parameter(ClassName) to All Custom Exceptions that you are throwing and that might be little extra code but i think that is the way. Hope it helps now.

Resources