How to mock custom wrapper object in Service layer of SpringBoot - spring-boot

I want to unit test my service layer code using junit and mockito.Now in my service layer method instead of returning directly dao layer response,I am returning a wrapper response in following way
public MyResponseClass fetchMyEntityRecords(){
List<MyEntity> myEntities = myDao.findAll();
List<MyDto> myDtoList = new ArrayList<MyDto>();
MyResponseClass obj = new MyResponseClass();
if(myEntities!=null && !myEntities.isEmpty()){
obj.setStatus("Success")
obj.setMessage("Data Found");
for(MyEntity myEntity : myEntities){
MyDto myDto = new MyDto();
BeanUtils.copyProperties(myEntity,myDto);
myDtoList.add(myDto);
}
obj.setData(myDtoList)
}else{
obj.setStatus("Failure")
obj.setMessage("Data Not Found");
obj.setData(Collections.emptyList());
}
return obj;
}
Now in Unit testing time
#Mock
private MyDao myDao;
#InjectMocks
private MyService myService;
#Test
void listOfMyEntityTest() {
MyEntity myEntity1= new MyEntity();
//setter method's invoked for setting value to enity1 object
MyEntity myEntity2= new MyEntity();
//setter method's invoked for setting value to enity2 object
when(myDao.findAll()).thenReturn(List.of(enity1,enity2));
var response = myService.fetchMyEntityRecords();
Assertions.assertThat(response.getData().size()>0).isTrue();
}
I am getting following error
Expecting value to be true but was false
org.opentest4j.AssertionFailedError:
Expecting value to be true but was false
Now the response is actually MyResponseClass.Now my unit test is failing because i am unable to mock this MyResponseClass. How to achieve it?

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

Dependency injection with mockito example

I am very new with Mockito and I don't get the following example (classes were provided, only test to write) and how to solve it.
What I try to do is use a test double for the supplier so that we can control the returned greeting in the test and assert that the GreetingService does not modify the greeting message in any way. Then assert that the returned greeting string is equal to "Hello Andy.".
public class Greeting {
private final String template;
public Greeting(String template) {
this.template = template;
}
public String forName(String world) {
return String.format(template, world);
}
}
#Component
public class GreetingService {
private final Supplier<Greeting> greetingSupplier;
public GreetingService(Supplier<Greeting> greetingSupplier) {
this.greetingSupplier = greetingSupplier;
}
public String greet(String name) {
return greetingSupplier.get().forName(name);
}
}
#Component
public class RandomGreetingSupplier implements Supplier<Greeting> {
private final List<Greeting> greetings = Arrays.asList(
new Greeting("Hello %s."),
new Greeting("Hi %s!"),
);
private final Random random = new Random();
#Override
public Greeting get() {
return greetings.get(random.nextInt(greetings.size()));
}
}
#SpringBootTest
public class GreetingServiceTest {
#Autowired
GreetingService greetingService;
#MockBean
Supplier<Greeting> greetingSupplier;
#Test
void getGreetingForPerson() {
String name = "Andy";
// that test cannot know which greeting will be returned by the supplier
// WHY IS IT NULLPOINTEREXCEPTION AFTER INITIALIZING #MockBean
//String greeting = greetingService.greet(name);
//assertThat(greeting).contains(name);
// WROTE SUCH TEST HERE -> NullPointerException WHY?
Mockito.when(greetingSupplier.get().forName(name)).thenReturn("Hello %s.");
assertThat(greetingSupplier.equals("Hello Andy."));
// THIS IS WORKING & TEST PASSED BUT I GUESS ITS WRONG?
Mockito.when(greetingSupplier.get()).thenReturn(new Greeting("Hello %s."));
assertThat(greetingSupplier.equals("Hello Andy."));
}
}
Mockito.when(greetingSupplier.get().forName(name)).thenReturn("Hello %s.");
You can't chain calls like that, you need to produce intermediate results, like
Supplier<Greeting> supplier = mock(Supplier.class);
Mockito.when(supplier).forName().thenReturn("Hello %s.");
Mockito.when(greetingSupplier.get()).thenReturn(supplier);
For dependency injection, you need to create the subject under test with the mocked Supplier. You can do that in a #Before method for example.
Your mocking is wrong.
Mockito.when(greetingSupplier.get().forName(name)).thenReturn("Hello %s.");
You mocked Supplier<Greeting> and the default behavior is to return null. So when you call greetingSupplier.get() in your first line it returns null. You directly chain forName which nou basicall is null.forName which leads to an error.
Your second part is actually (kind of) correct.
Mockito.when(greetingSupplier.get()).thenReturn(new Greeting("Hello %s."));
You now properly return a response from greetingSupplier.get(). Instead of chaining the call.
However I would argue that your excercise is wrong. Why? When using a Supplier<?> in Spring it actually is a lazy beanFactory.getBean call. You can lazily inject dependencies this way. You should have a mock for Greeting which returns a hardcoded String which you can check.

Mockito mock CassandraOperation Slice method

I am using the Mockito framework to write the mock Junit test cases below is my Service class to test the Junit test code
public class UserService {
#Autowired
private CassandraOperations template
public UserDTO getUserDTO(String date, String pagingState) {
Select select = QueryBuilder.select().all()
.from("tbl_user");
select.where(QueryBuilder.eq(date, date));
select.setFetchSize(30);
if (pagingState != null) {
select.setPagingState(PagingState.fromString(pagingState));
}
Slice<UserDTO> usgerDTO = template.slice(select, UserDTO.class);
if(usgerDTO.hasNext()) {
}
return usgerDTO.get();
}
Test case Class is writted
#RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
#InjectMocks
private UserService service ;
#Mock
private CassandraOperations template;
#Mock
private UserDTO userDTO;
private String date= "2019-09";
#Test(expected = Exception.class)
public void test() {
Slice<UserDTO> userDTO= Mockito.mock(Slice.class);
Select select = Mockito.mock(Select.class);
Mockito.when(template.slice(select, UserDTO.class)).thenReturn(userDTO);
metricReportDTO = service.getUserDTO(date, null);
}
}
I am getting Null values on this line
Slice<UserDTO> usgerDTO = template.slice(select, UserDTO.class);
Can you please helm me how to test template.slice method
Note: I should not use the PowerMockito
Your code snippet doesn't make sense in that you call service.getMetricsReports(date, null) in your test but your UserService code example only has the method getUserDTO. Assuming that is just a typo then you need to use matchers to get the mock to work in your when.
Mockito.when(template.slice(any(Select.class), eq(UserDTO.class))).thenReturn(userDTO);
This means whenever the first param is a class of type Select and the second is equal to UserDTO.class then return the userDTO if those 2 params don't match those conditions it will return null.
You can also make it match the exact Select assuming it has an overridden equals method. In the case where you send a null pagingState then something like:
Select stubSelect = QueryBuilder.select().all()
.from("tbl_user");
stubSelect.where(QueryBuilder.eq(date, date));
stubSelect.setFetchSize(30);
Mockito.when(template.slice(eq(stubSelect), eq(UserDTO.class))).thenReturn(userDTO);

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

Resources