Object reference changing during Mockito Test - spring

I have just started working with Mockito and am having a problem with one of the tests failing, yet the actual code works correctly in a live environment. The controller being tested is:
#Controller
#RequestMapping("/notes")
public class NotesController {
private NoteRepository noteRepository;
private MyUserRepository userRepository;
#RequestMapping(value = "/add", method = POST)
public String postNote(#Valid Note note, BindingResult errors, Principal principal){
String username = principal.getName();
MyUser user = userRepository.findUserByUsername(username);
note.setMyUser(user);
note.setTime(new Date());
noteRepository.save(note);
return "redirect:/notes"; }
}
The test is here:
#Test
public void testShouldAddValidNote() throws Exception {
MyUser testing = new MyUser();
Note note = new Note();
NoteRepository noteRepository = mock(NoteRepository.class);
when(noteRepository.save(note)).thenReturn(note);
MyUserRepository userRepository = mock(MyUserRepository.class);
when(userRepository.findUserByUsername("testing")).thenReturn(testing);
Principal mockPrincipal = mock(Principal.class);
when(mockPrincipal.getName()).thenReturn("testing");
NoteController controller = new NoteController(noteRepository);
controller.setMyUserRepository(userRepository);
MockMvc mockMvc = standaloneSetup(controller).build();
mockMvc.perform(post("/notes/add")
.requestAttr("note", note)
.principal(mockPrincipal))
.andExpect(view().name("redirect:/notes"));
verify(noteRepository,times(1)).save(note);
verify(note,times(1)).setMyUser(testing);
verify(note,times(1)).setTime((Date)anyObject());
verify(userRepository,times(1)).findUserByUsername("testing");
verify(mockPrincipal,times(1)).getName();
}
The first verify test fails, with the message:
Argument(s) are different! Wanted:
noteRepository.save(projectName.Note#5ae9);
Actual invocation has different arguments:
noteRepository.save(projectName.Note#c079ae45
Clearly the Note object passed in to the method has changed, but I thought that using .requestAttr("note", note) would pass in the reference and the same object should therefore be in the method (and later returned). Like I said, it works perfectly in the live web Container, so what am I getting wrong with the Mockito test please?

This is just a wild guess could the issue this code faces comes from MockMvc / MockMvcRequestBuilders where the Note is somehow serialized / deserialized between the request configuration and the actual request ?
Note that note is a real object so calling verify(note).... won't work.
Anyway I suggest the use of the combination of a mockito captor and AssertJ in this case :
// if field instantiation if using the mockito runner / rule or MockitoAnnotations.initMocks(this)
#Captor ArgumentCaptor<Note> noteCaptor;
// if created in the test method
ArgumentCaptor<Note> noteCaptor = ArgumentCaptor.forClass(Note.class);
// ...
verify(noteRepository,times(1)).save(noteCaptor.capture());
assertThat(noteCaptor.getValue().geMyUser()).isEqualTo(testing);
assertThat(noteCaptor.getValue().geTime()).isCloseTo(someDate);
Note I'm on a phone

Related

Can this method be tested using mockito?

I am not sure how to test the first method in the service layer with Mockito as it is using the helper method. Below is my failed attempt at a test: I get an InvalidUseOfMatchersException in the second when clause.
Thanks in advance!
#Mock
private EntityRepository EntityRepo;
#InjectMocks
private EntityService EntityService;
public List<DTO> getAllDTOs(){
//first method
return entityRepo.findAll()
.stream()
.map(this::convertEntityToDTO)
.collect(Collectors.toList());
}
//helper method
public DTO convertEntityToDTO(Entity entity) {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.LOOSE);
DTO dto = new DTO();
dto = modelMapper.map(entity, DTO.class);
return dto;
}
#Test
public void EntityService_GetAll_ReturnsDTOList() {
when(entityRepo.findAll()).thenReturn(Mockito.anyList());
//the second when clause: when(entityService.convertEntityToDTO(Mockito.any(Entity.class)))
.thenReturn(Mockito.any(DTO.class));
List<DTO>DTOList = entityService.getAllDTOs();
Assertions.assertThat(DTOList).isNotNull();
Mockito#any* methods are actually defined in class ArgumentMatchers and can only be used to match the call arguments when setting up a mock or when verifying calls. All matcher methods return null (but have side-effects of modifying a matcher stack to be able to properly detect and match mocked calls).
For instance, you might do Mockito.when(svc.print(Mockito.anyString()).doNothing() when you don't care about the input or Mockito.verify(svc.print(Mockito.anyString()), Mockito.never()) when you want to verify that the method has never been called.
When setting up your mock, you have to provide a real value in your thenReturn call:
when(entityRepo.findAll()).thenReturn(Collections.emptyList());
when(entityService.convertEntityToDTO(Mockito.any(Entity.class)))
.thenReturn(new DTO());

SOLVED. Mock service.insert() works with ArgumentMatchers.any() only, triggers exception otherwise

Im creating a test class using Mockito and everything is runnig OK, except by one mock that calls a service layer method and it only works if the input parameter is any() and it throws the following exception if the input parameter is native from the method.
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke "com.devsuperior.dscatalog.dto.ProductDTO.getId()" because "productDTO" is null at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
Lets go through the code:
The service layer method, productDTO is a DTO class from a ordinary Product entity.
#Transactional
public ProductDTO insert(ProductDTO productDTO) {
Product entity = new Product();
copyDtoToEntity(productDTO, entity);
entity = repository.save(entity); // reposity.save() returns a reference to object saved in DB
return new ProductDTO(entity);
}
the controller layer method:
#PostMapping
public ResponseEntity<ProductDTO> insert(#RequestBody ProductDTO productDTO){
productDTO = service.insert(productDTO); //[1]
URI uri = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(productDTO.getId()).toUri();
return ResponseEntity.created(uri).body(productDTO);
}
Before I continue I ran in debug mode this code and the ProdutDTO was correctly instanciated until the mock captured the service.insert(productDTO) call and after this line [1] productDTO = null
my test class:
#WebMvcTest(ProductResource.class)
public class ProductResourceTests {
#Autowired
private MockMvc mockMvc;
#MockBean
private ProductService service;
#Autowired
private ObjectMapper objectMapper;
private PageImpl<ProductDTO> page;
private ProductDTO productDTO;
private long existingId;
private long nonExistingId;
private long dependentId;
private long productBadCategoryId;
private String jsonBody;
#BeforeEach
void setUp() throws Exception {
productDTO = Factory.createProductDTO();
existingId = Factory.getExistingProductId();
nonExistingId = 100L;
dependentId = 50L;
productBadCategoryId = 200L;
page = new PageImpl<>(List.of(productDTO));
when(service.findAllPaged(ArgumentMatchers.any())).thenReturn(page);
when(service.findById(existingId)).thenReturn(productDTO);
when(service.findById(nonExistingId)).thenThrow(ResourceNotFoundException.class);
when(service.update(eq(existingId), any())).thenReturn(productDTO);
when(service.update(eq(nonExistingId), any())).thenThrow(ResourceNotFoundException.class);
when(service.update(eq(productBadCategoryId), any())).thenThrow(NestedResourceNotFoundException.class);
doNothing().when(service).delete(existingId);
doThrow(ResourceNotFoundException.class).when(service).delete(nonExistingId);
doThrow(DatabaseException.class).when(service).delete(dependentId);
when(service.insert(any(ProductDTO.class))).thenReturn(productDTO); // IT WORKS! [2]
//when(service.insert( productDTO )).thenReturn(productDTO); // IT IT DOESNT WORKS! [3]
jsonBody = objectMapper.writeValueAsString(productDTO);
}
and finally the test method where the mock call triggers the exception
#Test
public void insertShouldReturnProductDTOCreated() throws Exception {
ResultActions result = mockMvc.perform(post("/products").content(jsonBody)
.contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON));
result.andExpect(status().isCreated());
result.andExpect(jsonPath("$.id").exists());
result.andExpect(jsonPath("$.name").exists());
result.andExpect(jsonPath("$.description").exists());
}
Whenever I uncomment line [3] and comment line [2] the exception above is rised.
Of course I could let this way (its working), but If I want to raise an exception for the case the object to be inserted has some issue,I could build a new service.insert() mock to test the throw of the exception. As it is I canĀ“t because I cant diferentiate a any() object from another one.I have read some similar problems like mine and the proposed solution was to add #Autowired annotation with service variable, but in my case still the issue remains.
The solution and a tentative of explanation what was happening:
Override HashCode/Equals methods in ProducDTO class.
When serializing ProductDTO class (this line-> jsonBody = objectMapper.writeValueAsString(productDTO) ) and upon reception on resource layer it turns again into an object but its new reference address it is not the same and for instance the non override equals will fail (it compares the reference address and not their content).

Spring MVC Unit Testing - Mocked Service method is not called

I was working on a spring boot project where I have controller which calls a service method and process the output.
I'am using spring MockMvc for testing the web layer. In my test class I have mocked the service method with Mockito.when(). But when I call the corresponding handler method it is not calling the mocked service method instead returns a null response.
Controller
#Controller
public class SocialLoginEndpoints {
#Autowired
#Qualifier("facebookAuth")
SocialLogin faceBookAuth;
#Autowired
#Qualifier("googleAuth")
SocialLogin googleAuth;
#Autowired SignupService signupService;
#GetMapping("/auth/google")
public String googleAuth(#RequestParam String signupType, HttpServletRequest request) {
return "redirect:" + googleAuth.getAuthURL(request, signupType);
}
}
Test Class
#WebMvcTest(SocialLoginEndpoints.class)
class SocialLoginEndpointsTest {
#Autowired MockMvc mockMvc;
MockHttpServletRequest mockHttpServletRequest;
#MockBean GoogleAuth googleAuth;
#MockBean FacebookAuth facebokAuth;
#MockBean SignupService signupService;
#BeforeEach
void setUp() {
mockHttpServletRequest = new MockHttpServletRequest();
}
#Test
void googleAuth() throws Exception {
Mockito.when(googleAuth.getAuthURL(mockHttpServletRequest, "free"))
.thenReturn("www.google.com");
mockMvc
.perform(MockMvcRequestBuilders.get("/auth/google").param("signupType", "free"))
.andExpect(MockMvcResultMatchers.redirectedUrl("www.google.com"))
.andExpect(MockMvcResultMatchers.status().isFound())
.andDo(MockMvcResultHandlers.print());
Mockito.verify(googleAuth, Mockito.times(1)).getAuthURL(mockHttpServletRequest, "free");
}
The reponse which is returned is
MockHttpServletResponse:
Status = 302
Error message = null
Headers = [Content-Language:"en", Location:"null"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
please help me to resolve this issue.
Thanks in Advance !
You stubbed googleAuth.getAuthURL incorrectly.
The MockHttpServletRequest you create in your test and use during stubbing is not the same instance as HttpServletRequest that is sent via MockMvc. Also, they are not equal to each other (Object.equals is used as it is not overriden)
By default Mockito uses equals to verify if arguments in a stubbing match those in the real call. Your stubbing params do not match call params, thus a default value for the method (null) is returned.
The simplest way to fix is to relax argument matchers.
Mockito.when(googleAuth.getAuthURL(
any(HttpServletRequest.class),
eq("free")))
.thenReturn("www.google.com");

How to mock beans Autowired on service layer in #WebMvcTest

I am testing a REST API's in Spring boot gradle app, my mocked service using #MockBean is returning null. This mocked service return null if there are some beans Autowired in service class(I used constructor injection).
Here is sample Code(Not compiled, only for understanding)
#RestController
#RequestMapping("/xxx")
class TestController {
private RetriveDataService retriveDataService;
public TestControllerx(RetriveDataService retriveDataService) {
this.retriveDataService = retriveDataService;
}
#PostMapping(value = "/yyy")
public MyResponseModel myMethod(#RequestBody MyRequestModel model) {
return retriveDataService.retriveData(model);
}
}
#Service
class RetriveDataService {
private TokenService tokenService;
public RetriveDataService(TokenService tokenService) {
this.tokenService = tokenService;
}
public MyResponseModel retriveData(MyRequestModel model) {
String accessToken = tokenService.getToken().getAccessToken();
return retriveData(model, accessToken);
}
}
#RunWith(SpringRunner.class)
#WebMvcTest(TestController.class)
public class TestControllerTest {
#Autowired
private MockMvc mvc;
#Autowired
private ObjectMapper objectMapper;
#MockBean
private RetriveDataService retriveDataService;
#Test
public void testRetriveData() throws Exception {
mvc.perform(MockMvcRequestBuilders.post("/xxx/yyy").content(objectMapper.writeValueAsString(new MyRequestModel()))
.contentType(MediaType.APPLICATION_JSON_UTF8)).andDo(MockMvcResultHandlers.print())
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));
}
}
When I run this test, i am getting following output(If my service do not need another bean, I am getting expected output)
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Due to this response i facing problem on line .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8));. also when i check response body(as body is also a null)
Sample project to reproduce the issue is here
Checking your repository confirmed assumption form the discussion in comments under question.
You specify expectations on your mock
MyModel requestMessage = new MyModel();
requestMessage.setMessage("Hello Request Post");
given(testService1.getMessage(requestMessage)).willReturn(responseMessage);
but the message received to in your controller in your #WebMvcTest is not equal to requestMessage specified in the test. This is due to the fact that MyModel class does not override equals method.
In this situation, Mockito will use its default behaviour:
By default, for all methods that return a value, a mock will return either null, a primitive/primitive wrapper value, or an empty collection, as appropriate. For example 0 for an int/Integer and false for a boolean/Boolean.
You have two options to fix the problem:
override equals (and hashCode) in your request class.
Get acquainted with argument matchers
More info on option 2.:
Technically, your expectation is equivalent to:
given(testService1.getMessage(ArgumentMatchers.eq(requestMessage)))
.willReturn(responseMessage);
You can use other matcher, or even define your own. This is useful if you cannot modify code of your argument's type (type coming from 3-rd party library etc).
For example, you can use ArgumentMatchers.any(MyModel.class))

Spring 5 with JUnit 5 + Mockito - Controller method returns null

I try to test a method named as loadData defined in MainController which returns result as a string. Despite that this method actually returns data when the web app runs on servlet container (or when I debug the code), no data returns when I invoke it from a test class based on JUnit 5 with Mockito.
Here is my configuration:
#ContextConfiguration(classes = {WebAppInitializer.class, AppConfig.class, WebConfig.class})
#Transactional
#WebAppConfiguration
public class TestMainController {
#InjectMocks
private MainController mainController;
private MockMvc mockMvc;
#BeforeEach
public void init() {
mockMvc = MockMvcBuilders.standaloneSetup(this.mainController).build();
}
#Test
public void testLoadData() throws Exception {
MvcResult mvcResult = mockMvc
.perform(MockMvcRequestBuilders.get("/loadData.ajax"))
.andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
Assertions.assertNotNull(mvcResult.getResponse().getContentAsString(), "response should not be null");
}
}
The test fails due to java.lang.NullPointerException as the this.mainController is null.
Environment Details:
Spring version: 5.0.3
JUnit version: 5.0.3
mockito version: 1.9.5
hamcrest version: 1.3
json-path-assert version: 2.2.0
Edit: Here is the loadData method of MainController:
#RequestMapping(value = "/loadData.ajax", method = RequestMethod.GET)
public String loadData(HttpServletRequest request, HttpServletResponse response) {
List list = mainService.loadData(); // starts a transaction and invokes the loadData method of mainDAO repository which basically loads data from the database
return JSONArray.fromObject(list).toString();
}
You can call controller method directly, just like we do for service method, but this is not recommended. Using MockMvc, you check for header and request param mapping are proper. Plus, you also check for end point mapping is correct. Plus the Request METHOD is also correct. All these you cannot test if you test your code by directly calling the controller method.
One thing you can try is, instead of creating new object inside the standalone context, use the Mock. i.e
mockMvc = MockMvcBuilders.standaloneSetup(this. mainController).build();
And while calling, do this
MvcResult mvcResult = mockMvc
.perform(MockMvcRequestBuilders.get("/loadData.ajax"))
.andExpect(MockMvcResultMatchers.status().isOk()).andReturn();
Assert , what you would like to
Assert.assertEquals("response does not match", mvcResult.getResponse().getContentAsString(),
"some expected response");
You are getting null or 400 or 404 http status ?
If you are getting 400, then please check the header and req. param if any are proper. If you are getting 404 then please check the URL path. /loadData.ajax , assuming this is your request mapping path in controller method.

Resources