Unit testing Spring REST API Service (Update (PUT Method)) - java

I'm trying to unit test a service for my controller in my API but i'm getting the following error :
2020-05-20 15:23:51.493 WARN 25469 --- [ main] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.http.ResponseEntity<com.tropicalia.meu_cardapio.domain.user.User> com.tropicalia.meu_cardapio.api.user.update.UserUpdateRest.update(com.tropicalia.meu_cardapio.domain.user.User,java.lang.Long)]
MockHttpServletRequest:
HTTP Method = PUT
Request URI = /users/89
Parameters = {}
Headers = [Content-Type:"application/json"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = com.tropicalia.meu_cardapio.api.user.update.UserUpdateRest
Method = com.tropicalia.meu_cardapio.api.user.update.UserUpdateRest#update(User, Long)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 400
Error message = null
Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :202
Actual :400
This is my test class :
#RunWith(SpringRunner.class)
#WebMvcTest(UserUpdateRest.class)
public class UpdateUserTest {
#Autowired
private MockMvc mvc;
#MockBean
private UserUpdateService service;
#Test
public void updateUser_whenPutUser() throws Exception {
User user = new User();
user.setName("Test Name");
user.setId(89L);
given(service.updateUser(user.getId(), user)).willReturn(user);
mvc.perform(put("/users/" + user.getId().toString())
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isAccepted())
.andExpect(jsonPath("name", is(user.getName())));
}
}
And this is my service
#Service
public class UserUpdateService {
#Autowired
UserRepository repository;
public User updateUser(Long id, User user) {
repository
.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found."));
return repository.save(user);
}
}
Would really appreciate if someone could help me with this one.
From what i understand, there's something wrong with the request body but i have no idea what to do to fix it.

As specified in the error message, requestbody is missing.
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing
All you need to do is add body content to the unit test like this
ObjectMapper mapper = new ObjectMapper();
mvc.perform(put("/users/" + user.getId().toString())
.contentType(MediaType.APPLICATION_JSON))
.content(mapper.writeValueAsString(user))
.andExpect(status().isAccepted())
.andExpect(jsonPath("name", is(user.getName())));
you can also pass content like this
.content("{\"id\":\"89\", \"name\":\"Test Name\"}")

Related

I have an error "Content type not set" when testing with junit

I use spring boot 2.7 and test with junit
during the execution of the tests I get an error :
the other tests work well.
controller
#PostMapping(value = "/employees")
public ResponseEntity<Employee> addEmployee(#Valid #RequestBody EmployeeDto employeeDto) {
Optional<Employee> employeeDb = employeeService.findByEmail(employeeDto.getEmail());
// must not exist in database
if (!employeeDb.isEmpty()) {
return new ResponseEntity<>(HttpStatus.CONFLICT);
}
// convertion dto -> model
Employee employee = employeeDto.toEmployee();
return new ResponseEntity<>(employeeService.save(employee), HttpStatus.OK);
}
controllerTest
#Test
void addEmployee() throws Exception {
when(employeeService.save(employeeDto.toEmployee())).thenReturn(employee);
when(employeeService.findByEmail(any(String.class))).thenReturn(Optional.of(employee));
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(employeeDto);
mockMvc.perform(
MockMvcRequestBuilders
.post(REST_URL)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.characterEncoding("utf-8")
.content(json)
)
.andExpect(status().isOk())
.andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.firstname", is(employee.getFirstname())))
.andExpect(jsonPath("$.lastname", is(employee.getLastname())))
.andExpect(jsonPath("$.email", is(employee.getEmail())));
}
the error : "Content type not set"
yet in the test I indicate well the content-type : ".contentType(MediaType.APPLICATION_JSON)"
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/employees/
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json", Content-Length:"74"]
Body = {"email":"test1#test.com","firstname":"firstname1","lastname":"lastname1"}
Session Attrs = {}
Handler:
Type = com.acme.app1.controllers.EmployeeController
Method = com.acme.app1.controllers.EmployeeController#addEmployee(EmployeeDto)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Content type not set
at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:37)
at org.springframework.test.util.AssertionErrors.assertTrue(AssertionErrors.java:70)
at org.springframework.test.util.AssertionErrors.assertNotNull(AssertionErrors.java:106)
for the test to be successful, what should I change?
The content type you've set is used for the request, but in your assertion you're checking for the content type which is returned in the response.
As you can see in the log, there is no content type or body set in the MockHttpServletResponse:
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
So there seems to be an issue with the returned object in your mocked result of
when(employeeService.save(employeeDto.toEmployee())).thenReturn(employee);
since the result of this will be rendered to a JSON object you're doing your assertions on.

Mock error UnfinishedStubbingException in a Junit test

I've created a test to try out my microservice. A simple get call that through 3 parameters returns me the message with the specifications of that user. I created it using Junit and Mockito. Below:
#Test
public void TestOk() throws Exception{
CarsRequest carsRequest = new CarsRequest();
carsRequest.setName(TestCostants.NAME);
carsRequest.setPlate(TestCostants.PLATE);
carsRequest.setPrice(TestCostants.PRICE);
Cars car = new Cars("BMW","TG35647", "15000","re",80000000,
null,null);
List<Cars> cars = new ArrayList<>();
cars.add(car);
Mockito.when(
service.getByPlate(carsRequest.getName(), carsRequest.getPlate(),
carsRequest.getPrice())).thenReturn(cars);
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/resources/cars").accept(
MediaType.APPLICATION_JSON).header("Authorization","Basic //myAuth");
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
System.out.println(result.getResponse());
String expected = "{\"name\"=\"BMW\", \"plate\"=\"TG35647\", " +
"\"price\"=\"15000\",\"brand\"=\"re\", \"kilometers\"=\"80000000\", \"revisiondate\"=\"null\", \"owner\"=\"null\"}";
JSONAssert.assertEquals(expected, result.getResponse()
.getContentAsString(), false);
});
clearly the constants of the variables that are passed conform to those of the message that must return me. When I try to start the test it gives me an error
This is the stacktrace I am reporting:
2021-03-29 12:45:07.906 [main] INFO o.s.s.l.SpringSecurityLdapTemplate - Ignoring PartialResultException
{
"#timestamp" : "2021-03-29T12:45:08.119+02:00",
"#version" : "1",
"message" : "[GET] http://localhost/resources/cars/ call resulted in the following error: Content type '' not supported",
"logger_name" : "my.project.car.controller.ExceptionHandlingController",
"thread_name" : "main",
"level" : "ERROR",
"level_value" : 40000
}
ErrorResponse: org.springframework.web.HttpMediaTypeNotSupportedException: Content type '' not supported
org.springframework.mock.web.MockHttpServletResponse#135f160e
MockHttpServletRequest:
HTTP Method = GET
Request URI = /resources/cars/
Parameters = {}
Headers = [Accept:"application/json", Authorization:"Basic //myAuth"]
Body = null
Session Attrs = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.HttpMediaTypeNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 500
Error message = null
Headers = [Content-Type:"application/json", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
Content type = application/json
Body = {"esito":"KO","codiceEsito":"ERROR","descrizioneEsito":"Errore generico"}
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError:
Expected: name
but none found
;
Expected: plate
but none found
;
Expected: price
but none found
;
Expected: brand
but none found
;
Expected: kilometers
but none found
;
Expected: revisiondate
but none found
;
Expected: owner
but none found
;
Trying to go to Debug, I get this error back, but I don't know how to handle it at all
Method threw 'org.mockito.exceptions.misusing.UnfinishedStubbingException' exception. Cannot evaluate my.project.service.CarService$MockitoMock$1824138024.toString()
I guess the two errors are related but I don't know how to fix this.
EDIT:
As requested, I add my Car Service:
#Service
public class CarService {
#Autowired
CarRepository carRepository;
#Autowired
ObjectMapper objectMapper;
public List<Car> getByPlate(String name, String plate, String price) throws JsonProcessingException {
List<Car> car = new ArrayList<>();
for (Cache.Entry<String, Car> entry: carRepository.findAllByNameAndPlateAndPrice(name, plate,price)){
car.add(new Car(entry.getKey(), entry.getValue()));
System.out.println("Entry: " + objectMapper.writeValueAsString(entry));
}
return cars;
}
}
and my CarController
#RestController
#RequestMapping("/resources/")
public class CarController {
#Autowired
CarService carService;
#Autowired
ObjectMapper objectMapper;
#GetMapping(
value = "/cars",
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE})
public CarsResponse getCars(#RequestBody CarsRequest request) throws IOException {
//some code
}
First of all, using GET requests with a body is the poor practice. Use POST requests instead.
Then, spring tells you about org.springframework.web.HttpMediaTypeNotSupportedException and in your controller method getCars() you describe the next:
consumes = {MediaType.APPLICATION_JSON_VALUE},
produces = {MediaType.APPLICATION_JSON_VALUE}
But in your test you don't specify any content-type:
RequestBuilder requestBuilder = MockMvcRequestBuilders.get(
"/resources/cars").accept(
MediaType.APPLICATION_JSON).header("Authorization","Basic //myAuth");
Try to add content-type .contentType(MediaType.APPLICATION_JSON):
RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/resources/cars")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Basic //myAuth");
Also, I don't see how you pass the body to the request. Try to add it .content(mapper.writeValueAsString(carsRequest)):
RequestBuilder requestBuilder = MockMvcRequestBuilders.get("/resources/cars")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Basic //myAuth")
.content(mapper.writeValueAsString(carsRequest));
where mapper is ObjectMapper. I assume you are using com.fasterxml.jackson.databind.ObjectMapper.

How to test spring boot rest controller with Junit 5

I'm having an extremely hard time finding consistent resources on how to actually code Junit5 tests for Sprint Boot. Right now, I have have a very simple controller and my attempt at a Junit5 test, but I'm not even able to get the result back from a simple rest call. It's suppose to return an array of string values. I believe the main problem is the MockHttpServletResponse Header isn't appending a port on there, but I'd like it to be dynamically added so whoever pulls down the code can Junit5 test it just fine.
#RestController
public class CnlLetterController {
#Autowired
private CnlLetterService cnlLetterService;
#Autowired
private UserService userService;
#Autowired
private ServiceLetterService serviceLetterService;
#GetMapping("/rest/cnl/templates")
public List<String> findTemplates () {
return cnlLetterService.getTemplates();
}
/* Other methods that don't matter right now */
}
TestController
#ActiveProfiles("dev")
#ExtendWith(SpringExtension.class)
#WebMvcTest(CnlLetterController.class)
class CnlLetterControllerTest {
#MockBean
private ClientRegistrationRepository crr;
#MockBean
private CnlLetterService cnlLetterService;
#MockBean
private UserService userService;
#MockBean
private ServiceLetterService serviceLetterService;
#Autowired
private MockMvc mockMvc;
#Test
void testFindTemplates() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/pis/rest/cnl/templates")
.accept(MediaType.APPLICATION_JSON))
.andDo(print()).andExpect(status().isOk());
}
}
Junit console output
MockHttpServletRequest:
HTTP Method = GET
Request URI = /pis/rest/cnl/templates
Parameters = {}
Headers = [Accept:"application/json"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 302
Error message = null
Headers = [Location:"https://localhost/pis/rest/cnl/templates"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = https://localhost/pis/rest/cnl/templates
Cookies = []
MockHttpServletRequest:
HTTP Method = GET
Request URI = /pis/rest/cnl/templates
Parameters = {}
Headers = [Accept:"application/json"]
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 302
Error message = null
Headers = [Location:"https://localhost/pis/rest/cnl/templates"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = https://localhost/pis/rest/cnl/templates
Cookies = []
2020-07-20 13:37:29.648 INFO 12868 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor'

Spring Boot RestController testing returns 404

I'm trying to test my code (Spring-Boot project) of a RestController, but I always get 404.
Here is what I have so far:
#RestController("/service")
public class ServiceInteractionController {
#Autowired
private PairingService pairingService;
#GetMapping("/registered/{sensorId}")
public ResponseEntity isSensorRegistered(#PathVariable String sensorId) {
return ResponseEntity.ok(pairingService.isSensorRegistered(sensorId));
}
}
#RunWith(SpringRunner.class)
#WebMvcTest(ServiceInteractionController.class)
public class ServiceInteractionControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private PairingService pairingService;
#Before
public void setUp() {
Mockito.when(pairingService.isSensorRegistered(TestConstants.TEST_SENSOR_ID))
.thenReturn(true);
}
#Test
public void testIsSensorRegistered() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("service/registered/{sensorId}", TestConstants.TEST_SENSOR_ID))
.andExpect(MockMvcResultMatchers.status().isOk());
}
}
The result always looks like this:
MockHttpServletRequest:
HTTP Method = GET
Request URI = service/registered/test123Id
Parameters = {}
Headers = []
Body = <no character encoding set>
Session Attrs = {}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 404
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Status
Expected :200
Actual :404
What am I doing wrong? I already tried to initialize mockmvc directly in setUp method with standaloneSetup() and I have also used #SpringBootTest combined with #AutoConfigureMockMvc.
Does anyone have some useful hints? I use spring boot 2.1.4.
Thanks!
Don't you miss the "/" before the service/registered/{sensorId}?
mockMvc.perform(MockMvcRequestBuilders.get("service/registered/{sensorId}", TestConstants.TEST_SENSOR_ID))
.andExpect(MockMvcResultMatchers.status().isOk());
Try changing this function to read:
#PathVariable
#Test
public void testIsSensorRegistered(#PathVariable sensorId ) throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("service/registered/{sensorId}", TestConstants.TEST_SENSOR_ID))
.andExpect(MockMvcResultMatchers.status().isOk());
}

Springboot test how to resolve controller's parameter to object

I have a method signature like this in controller. when i try to write a unit test for it. it returns 500 instead of 404.
it looks like it is not able to convert the {id} to an Optional
is there any setting I need to do so it can auto convert the parameter to an object?
Thanks
#RequestMapping("/propagationStores")
public class PropagationStoreController {
private StoreRepository storeRepository;
private CustomValidator validator;
public PropagationStoreController(StoreRepository storeRepository) {
this.storeRepository = storeRepository;
}
#GetMapping(value = "/{id}")
public Resource<StoreDto> getById(#PathVariable("id") Optional<Store> storeOptional) {
return storeOptional
.map(StoreConverter::toDto)
.map(store -> {
Resource<StoreDto> resource = new Resource<>(store);
resource.add(new Link("http://localhost").withTitle("localhost"));
return resource;
}).orElseThrow(ResourceNotFoundException::new);
}
when I try to test the getById method using the following code. I am getting 500 instead of 400
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureMockMvc
public class PropagationStoreControllerIT {
#MockBean
StoreRepository storeRepository;
#MockBean
CustomValidator customValidator;
#Autowired
private MockMvc mockMvc;
#Test
public void testGetById() throws Exception {
when(storeRepository.findById(1l)).thenReturn(Optional.empty());
mockMvc.perform(get("/propagationStores/1")).andDo(print()).andExpect(status().is4xxClientError());
}
}
I was expecting status 404, but I am getting 500.
the log as the following.
MockHttpServletRequest:
HTTP Method = GET
Request URI = /propagationStores/1
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = local.tux.propagation.controller.PropagationStoreController
Method = public org.springframework.hateoas.Resource<local.tux.propagation.dto.Store$StoreDto> local.tux.propagation.controller.PropagationStoreController.getById(java.util.Optional<local.tux.propagation.evaluator.domain.Store>)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 500
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
MockHttpServletRequest:
HTTP Method = GET
Request URI = /propagationStores/1
Parameters = {}
Headers = []
Body = null
Session Attrs = {}
Handler:
Type = local.tux.propagation.controller.PropagationStoreController
Method = public org.springframework.hateoas.Resource<local.tux.propagation.dto.Store$StoreDto> local.tux.propagation.controller.PropagationStoreController.getById(java.util.Optional<local.tux.propagation.evaluator.domain.Store>)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.web.method.annotation.MethodArgumentConversionNotSupportedException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 500
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
java.lang.AssertionError: Range for response status value 500
Expected :CLIENT_ERROR
Actual :SERVER_ERROR
Define your controller method as:
public Resource<StoreDto> getById(#PathVariable("id") Optional<String> id) {
......
}
id can be converted to a string or a number, not into a Store class.
I was able to solve the issue by using adding the #TestConfiguration. It looks like
#MockBean interrupt the normal spring boot initialization, it doesn't register the converter. In order to make it work, we need to register ourself.
#TestConfiguration
static class InternalConfig {
#Bean
WebMvcConfigurer configurer() {
return new WebMvcConfigurer() {
#Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(String.class, Store.class, id -> staticRepository.getOne(Long.parseLong(id)));
}
};
}
}

Categories