Spring Boot RestController testing returns 404 - java

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());
}

Related

How to test InternalServerError using mockito in Repository?

I am writing a test to test the POST method for failure case in the controller.
It returns a 415, I am expecting 500. I have mocked the response using mockito.
ControllerTest
#Test
#DisplayName("POST /customers - Failure")
void createProductShouldFail() throws Exception {
// Setup mocked service
when(customerService.save(any(Customer.class))).thenThrow(HttpServerErrorException.InternalServerError.class);
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/customers").accept(MediaType.APPLICATION_JSON)
.content("{\"name\":\"John\"}");
MvcResult result=mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = result.getResponse();
// Validate the response code and content type
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), response.getStatus());
}
Controller
#PostMapping(path = "/customers")
public ResponseEntity<Customer> saveCustomer(#RequestBody Customer customer){
try {
// Create the new product
Customer savedCustomer = customerService.save(customer);
// Build a created response
return ResponseEntity
.created(new URI("/customers/" + savedCustomer.getId()))
.body(savedCustomer);
} catch (URISyntaxException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
Error:
HTTP Method = POST
Request URI = /customers
Parameters = {}
Headers = [Accept:"application/json", Content-Length:"15"]
Body = {"name":"John"}
Session Attrs = {}
Handler:
Type = com.prabhakar.customer.controller.CustomerController
Method = com.prabhakar.customer.controller.CustomerController#saveCustomer(Customer)
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 = 415
Error message = null
Headers = [Accept:"application/json, application/*+json"]
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
org.opentest4j.AssertionFailedError:
Expected :500
Actual :415
But 415-Unsupported Media Type client error response code.
I have used the same payload for this method,it works.
#Test
#DisplayName("POST /customers - Success")
void createProductShouldSucceed() throws Exception {
// Setup mocked service
Customer mockCustomer = new Customer(1L, "John");
when(customerService.save(any(Customer.class))).thenReturn(mockCustomer);
this.mockMvc.perform(post("/customers")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"John\"}"))
// Validate the response code and content type
.andExpect(status().isCreated())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
//Validate returned json fields
.andExpect(jsonPath("$.id").value(1L))
.andExpect(jsonPath("$.name").value("John"));
}
Update I have added
#RestController
#EnableWebMvc
this throws an error as mocked But the code breaks near mockmvc.perform.
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.client.HttpServerErrorException$InternalServerError
How can I verify if this is working.
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), response.getStatus());
There are two thing you must have in account to solve the problem:
First, Instead of use .accept(MediaType.APPLICATION_JSON) you must use .contentType(MediaType.APPLICATION_JSON).
Second, the other thing you must have in account is, if you are not handling the exception (using a controller advice or other way) you must do it, because when you execute the firts step you will receive the following error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.web.client.HttpServerErrorException$InternalServerError
The workaround that I took was use #ExceptionHandler in the CustomerController to test your code (this isn't the best place to do this, depending what you are doing. Instead use a #ControllerAdvice. You can find some examples here https://www.baeldung.com/exception-handling-for-rest-with-spring).
Below the complete code that are used to recreate your case.
Customer.class
public class Customer {
private Long id;
private String name;
public Customer(Long id, String name) {
this.id = id;
this.name = name;
}
// + getter and setter
}
CustomerController.class
#RestController
public class CustomerController {
private final CustomerService customerService;
public CustomerController(CustomerService customerService) {
this.customerService = customerService;
}
#PostMapping(path = "/customers")
public ResponseEntity<Customer> saveCustomer(#RequestBody Customer customer) {
try {
// Create the new product
Customer savedCustomer = customerService.save(customer);
// Build a created response
return ResponseEntity
.created(new URI("/customers/" + savedCustomer.getId()))
.body(savedCustomer);
} catch (URISyntaxException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
// Code used to avoid the error explained in the second step
#ExceptionHandler
public ResponseEntity<?> handlingInternalServerError(HttpServerErrorException.InternalServerError ex) {
// code to be executed when the exception is thrown (logs, ...)
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
CustomerService.class
#Service
public class CustomerService {
public Customer save(Customer customer) {
return customer;
}
}
CustomerControllerTest.class
#SpringBootTest
#AutoConfigureMockMvc
class CustomerControllerTest {
#MockBean
private CustomerService customerService;
#Autowired
private MockMvc mockMvc;
#Test
#DisplayName("POST /customers - Failure")
void saveCustomer() throws Exception {
Customer customerMock = new Customer(1L, "John");
// Setup mocked service
when(customerService.save(any(Customer.class))).thenThrow(HttpServerErrorException.InternalServerError.class);
RequestBuilder requestBuilder = post("/customers")
.content("{\"name\":\"John\"}")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = result.getResponse();
// Validate the response code and content type
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(), response.getStatus());
}
}
NOTE: This test was executed using Java 8 and JUnit5
Other NOTE based on your comment:
Ok. #prabhakar-maity, my recommendation based in your case is to use a #ExceptionHandler or #ControllerAdvice instead of try...catch. For example, you have 6 methods in your controller or several controllers and want to handle the same exception (Internal Server Error) and return the same info, so you’ll have to implement a try..catch in each method, while using #ControllerAdive (multiple controllers) or #ExceptionHandler (one controller) you implement your logic in one place
Check this question for more info LINK
You can reference Spring MVC Test Framework - Unsupported Media Type
You may be missing #EnableWebMvc annotation in your controller.
EDIT - for Comment:
Instead of
RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/customers").accept(MediaType.APPLICATION_JSON)
.content("{\"name\":\"John\"}");
MockHttpServletResponse response = result.getResponse();
// Validate the response code and content type
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR.value(),
response.getStatus());
Try:
mockMvc.perform(requestBuilder)
.andExpect(status().isInternalServerError());

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'

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

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\"}")

MockMVC test with Custom Authentication Principal

I am trying to write a test for an API endpoint but can not get past the security. At least that's what I understand is happening.
From what I understood in the docs (https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/test-method.html) I think I am following the right way?
My Rest controller looks as following. I have an Authentication Principal which I believe is causing the problem.
#RestController
#RequestMapping("/api")
public class VehicleResource {
#GetMapping(value = "/status", produces = { MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<StatusResponse> getStatus(#AuthenticationPrincipal CustomUser customUser) {
StatusResponse statusResponse = vehicleService.getStatus(customUser.getVin());
LOGGER.debug("Sent statusResponse: {}", statusResponse);
return new ResponseEntity<>(statusResponse, HttpStatus.OK);
}
My test is fairly simple:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(classes = { MobileBackendApplication.class })
#AutoConfigureMockMvc
public class VehicleResourceTest {
#Autowired
private MockMvc mockMvc;
private AccountCredentials testCredentials;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
testCredentials = new AccountCredentials("test.user#test.com", "testPassword");
}
#Test
#WithMockCustomUser
public void getStatusTest() throws Exception {
mockMvc.perform(
get("/api/status").with(httpBasic(testCredentials.getUsername(), testCredentials.getPassword())))
.andExpect(status().isOk());
}
I have a MockCustomUser interface so I can pass along custom users:
#WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public #interface WithMockCustomUser {
String username() default "test.user#bmw.com";
String password() default "testPassword";
String vin() default "123TEST456VIN789";
String pushNotificationId() default "testPushId";
}
With the WithMockCustomUserSecurityContextFactory:
public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> {
#Override
public SecurityContext createSecurityContext(WithMockCustomUser customUser) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
CustomUser principal = new CustomUser(customUser.username(), customUser.password(), customUser.vin(),
new ArrayList<String>(Arrays.asList(customUser.pushNotificationId())), null,
AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
Authentication auth = new UsernamePasswordAuthenticationToken(principal, "password",
principal.getAuthorities());
context.setAuthentication(auth);
return context;
}
}
When I run the test I receive a 401 Unauthorized instead of a 200. Other answers referred to maybe missing the Basic Authentication header, but I seem to be passing that along. Below is what is printed when I run the test.
MockHttpServletRequest:
HTTP Method = GET
Request URI = /api/status
Parameters = {}
Headers = {Authorization=[Basic dGVzdC51c2VyQHNhbXBsZS5jb206dGVzdFBhc3N3b3Jk]}
Body = null
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 = 401
Error message = Unauthorized
Headers = {Access-Control-Allow-Origin=[*], Access-Control-Allow-Methods=[POST, GET], Access-Control-Max-Age=[3600], Access-Control-Allow-Headers=[Origin, X-Requested-With, Content-Type, Accept, Authorization], Access-Control-Expose-Headers=[Authorization], WWW-Authenticate=[Basic realm="Realm"], 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 = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []

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