i created post method in mockMVC (in spring boot project)
This is my method testing
This is my method testing
#Test
public void createAccount() throws Exception {
AccountDTO accountDTO = new AccountDTO("SAVINGS", "SAVINGS");
when(addaccountService.findByName("SAVING")).thenReturn(Optional.empty());
when(addaccountService.createAccount(any())).thenReturn(createdAccountDTO);
CreatedAccountDTO createdAccountDTO = new CreatedAccountDTO("a#wp.pl", "SAVINGS", "1234rds", uuid);
mockMvc.perform(
post("/account").contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(AccountNewDTO)))
.andExpect(status().isCreated())
.andExpect(header().string("location", containsString("/account/"+uuid.toString())));
System.out.println("aaa");
}
I want to write GET method.
how to write a get method in mock mvc? how to verify whether what I threw was returned?
You can try the below for Mockmvc perform get and post methods
For get method
#Autowired
private MuffinRepository muffinRepository;
#Test
public void testgetMethod throws Exception(){
Muffin muffin = new Muffin("Butterscotch");
muffin.setId(1L);
BddMockito.given(muffinRepository.findOne(1L)).
willReturn(muffin);
mockMvc.perform(MockMvcRequestBuilders.
get("/muffins/1")).
andExpect(MockMvcResutMatchers.status().isOk()).
andExpect(MockMvcResutMatchers.content().string("{\"id\":1, "flavor":"Butterscotch"}"));
}
//Test to do post operation
#Test
public void testgetMethod throws Exception(){
Muffin muffin = new Muffin("Butterscotch");
muffin.setId(1L);
BddMockito.given(muffinRepository.findOne(1L)).
willReturn(muffin);
mockMvc.perform(MockMvcRequestBuilders.
post("/muffins")
.content(convertObjectToJsonString(muffin))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResutMatchers.status().isCreated())
.andExpect(MockMvcResutMatchers.content().json(convertObjectToJsonString(muffin)));
}
If the response is empty then make sure to override equals() and hashCode() method on the Entity your repository is working with
//Converts Object to Json String
private String convertObjectToJsonString(Muffin muffin) throws JsonProcessingException{
ObjectWriter writer = new ObjectWriter().writer().withDefaultPrettyPrinter();
return writer.writeValueAsString(muffin);
}
You can use the static get method of the class MockMvcRequestBuilders, see: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.html#get-java.lang.String-java.lang.Object...-
Example:
mockMvc.perform(get("/account")).andExpect(...);
If you throw an exception within your controller method it will typically trigger execution of an exception handler which transforms the exception into a HTTP error response. By default, you could check if the status of the response was 500. If you have implemented your own exception handler you may want to check the response body as well to verify if it contains the expected error data.
Related
#PostMapping("/addCompany")
public Company createCompany(#Valid #RequestBody Company company, Errors errors) {
if (errors.hasErrors()) {
throw new ValidationException(errors.getFieldError().getDefaultMessage());
}
return companyService.addCompany(company);
}
You can test it in two ways:
unit test of controller
web layer test
Unit test:
Mock Errors and call createCompany with that mock as an argument.
Verify that exception is thrown
Web layer test:
post an invalid request with mockMvc
check that error code is returned
#WebMvcTest(CompanyController.class)
class CompanyControllerTest {
private static final String CREATE_COMPANY_ENDPOINT = "/addCompany/";
#Autowired
private MockMvc mockMvc;
#Test
void respondsWith4xxOnInvalidCreateCompanyRequest() throws Exception {
String invalidRequest = "{\"id\":\"aaa\", \"name\":\"bbb\"}"
MockHttpServletRequestBuilder builder = MockMvcRequestBuilders
.post(CREATE_COMPANY_ENDPOINT)
.content(invalidRequest)
.contentType(MediaType.APPLICATION_JSON_VALUE);
this.mockMvc.perform(builder)
.andExpect(status().is4xxClientError());
}
}
In this case, I have a strong preference to web layer test - you check actual validation logic. Mocking Errors buys you nothing except code coverage.
On top of that:
Your handling of validation errors is trivial. If you drop Errors from the parameter list, on invalid input Spring will throw MethodArgumentNotValidException with BindingResult, which has all the data you need.
I am pretty new to spring controller. I am trying to write unit test for invalid parameter. I have an api that has #RequestParam("id") #Min(1) long id and in my unit test, I pass in "-1". Here is my test:
#Test
public void searchWithInvalidIbId() throws Exception {
mockMvc.perform(get(BASE_URL)
.param(COLUMN_IB_ID, INVALID_IB_ID_VALUE) // = "-1"
.param(COLUMN_TIME_RANGE, TIME_RANGE_VALUE)
.param(COLUMN_TIME_ZONE, TIME_ZONE_VALUE)
.accept(PowerShareMediaType.PSH_DISPATCH_REPORTER_V1_JSON)
.contentType(PowerShareMediaType.PSH_DISPATCH_REPORTER_V1_JSON))
.andExpect(status().isBadRequest());
}
When I run this, I get
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is javax.validation.ConstraintViolationException: search.arg2: must be greater than or equal to 1
It makes sense, but I am not sure how to test this is BadRequest. I tried #Test(expected = NestedServletException.class), and it passed, but I don't think it is checking what I want to check. What is the right approach to check this?
You can have your custom exception handler annotated with #ControllerAdvice and handle ConstraintViolationException in that class. You can throw your custom exception with additional details if you wish.
Here is an example approach:
#ControllerAdvice
public class MyCustomExceptionHandler {
#ResponseBody
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ExceptionHandler(ConstraintViolationException.class)
ApiError constraintViolationException(ConstraintViolationException e) {
return BAD_REQUEST.apply(e.getBindingResult());
}
}
Here ApiError is a custom class to represent your error response, it can be anything else you want. You can add timestamp, http status, your error message etc.
I have implemented a Spring Rest Controller that streams back large files using the StreamingResponseBody. However, these files are coming from another system and there is the potential for something to go wrong while streaming them back. When this occurs I am throwing a custom Exception (MyException). I am handling the exception in an #ExceptionHandler implementation which is below. I am attempting to set the response httpstatus and error message but I am always receiving http status 406. What is the proper way to handle errors/exceptions while returning a StreamingResponseBody?
#ExceptionHandler(MyException.class)
public void handleParsException( MyException exception, HttpServletResponse response) throws IOException
{
response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(),exception.getMessage());
}
You should handle all errors in the same way. There are many options.
I prefer next:
Controller Advice
It is a good idea to have an entity to send a generic error response, an example:
public class Error {
private String code;
private int status;
private String message;
// Getters and Setters
}
Otherwise, to handle exceptions you should create a class annotated with #ControllerAdvice and then create methods annotated with #ExceptionHandler and the exception or exceptions (it could be more than one) you want to handle. Finally return ResponseEntity<Error> with the status code you want.
public class Hanlder{
#ExceptionHandler(MyException.class)
public ResponseEntity<?> handleResourceNotFoundException(MyException
myException, HttpServletRequest request) {
Error error = new Error();
error.setStatus(HttpStatus.CONFLICT.value()); //Status you want
error.setCode("CODE");
error.setMessage(myException.getMessage());
return new ResponseEntity<>(error, null, HttpStatus.CONFLICT);
}
#ExceptionHandler({DataAccessException.class, , OtherException.class})
public ResponseEntity<?> handleResourceNotFoundException(Exception
exception, HttpServletRequest request) {
Error error = new Error();
error.setStatus(HttpStatus.INTERNAL_ERROR.value()); //Status you want
error.setCode("CODE");
error.setMessage(myException.getMessage());
return new ResponseEntity<>(error, null, HttpStatus.INTERNAL_ERROR);
}
}
Other ways:
Annotate exception directly
Other way is annotating directly the excetion with the status and the reason to return:
#ResponseStatus(value=HttpStatus.CONFLICT, reason="Error with StreamingResponseBody")
public class MyError extends RuntimeException {
// Impl ...
}
Exception Handler in a specific controller
Use a method annotated with #ExceptionHandler in a method of a #Controller to handle #RequestMapping exceptions:
#ResponseStatus(value=HttpStatus.CONFLICT,
reason="Error with StreamingResponse Body")
#ExceptionHandler(MyError.class)
public void entitiyExists() {
}
I figured the problem out. The client was only accepting the file type as an acceptable response. Therefore, when returning an error in the form of an html page I was getting httpstatus 406. I just needed to tell the client to accept html as well to display the message.
The Spring method I wanna test
#RequestMapping(value="/files", method=RequestMethod.GET)
#ResponseBody
public List<FileListRequest> get() {
return getMainController().getAllFiles();
}
I want to be assured all calls to /files are responded with an List[FileListRequest]. How?
This is the method in which the test is supposed to be.
#Test
public void testGetAll() throws Exception {
this.mockMvc.perform(get("/files").accept("application/json"))
.andExpect(status().isOk())
.andExpect(content().contentType(SOMETHING);
}
Can I simply replace the SOMETHING or am I totally wrong?
Can I run assert methods on the object returned by perform()?
Edit:
MvcResult result = this.mockMvc.perform(get("/files").accept("application/json"))
.andExpect(status().isOk())
.andReturn();
String content = result.getResponse().getContentAsString();
// Convert json String to Respective object by using Gson or Jackson
ObjectMapper mapper = new ObjectMapper();
TypeFactory typeFactory=objectMapper.getTypeFactory();
List<SomeClass> someClassList =mapper.readValue(content , typeFactory.constructCollectionType(List.class, SomeClass.class));
//Assert here with your list
You could use Json Path to check if specific data exist in your response
a code snipper from by old project
mockMvc.perform(get("/rest/blogs")) .contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.blogs[*].title",
hasItems(endsWith("Title A"), endsWith("Title B"))))
.andExpect(status().isOk());
You cannot use contentType to check the class of instances. The Content-Type is to determine the format of text sent/returned in a HTTP(S) request/response, and has nothing to do with programmatic type-check. It only regulates that the request/response is in json/text-plain/xml, etc.
To check the type of the objects returned in the response, let's assume that the response is in format JSON(built-in Jackson in Spring boot will do the (un)marshalling), and we just use org.hamcrest.Matchers.instanceOf(Class<?> type) to check the class of first item in the list, with jsonPath.
A working snippet:
import static org.hamcrest.Matchers.instanceOf;
...
#Test
public void testBinInfoControllerInsertBIN() throws Exception {
when(this.repository.save(mockBinInfo)).thenReturn(mockBinInfo);
this.mockMvc.perform(post("/insert")
.content("{\"id\":\"42\", \"bin\":\"touhou\", \"json_full\":\"{is_json:true}\", \"createAt\":\"18/08/2018\"}")
.accept(MediaType.APPLICATION_JSON_UTF8_VALUE)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
)
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andExpect(status().isCreated())
.andExpect(jsonPath("$[0]", instanceOf(BinInfo.class)))
.andExpect(jsonPath("$[0].bin", is("touhou")));
}
If you want to check every item in the list... maybe it is redundant? I haven't seen code examining each and every item in the list because you have to iterate. There is way, of course.
I'm still learning my way around testing and I'm trying to get a MockMvc test to work for me. It's a simple REST controller that at this point is only doing some authentication using information from json in the post. I've actually implemented the code, so I know it's working because I get back both the correct response with the correct input and the error messages I've put together, both in a json format. My problem is that the test keeps failing with a HttpMessageNotReadableException, even though the actual code works, so I'm assuming I don't have my test set up right. Any help you guys can give would be great.
Here's my controller
#Controller
public class RequestPaymentController {
protected final Log logger = LogFactory.getLog(getClass());
private PaymentService paymentService;
private LoginService loginService;
#Autowired
public void setPaymentService(PaymentService paymentService){
this.paymentService = paymentService;
}
#Autowired
public void setLoginService(LoginService loginService){
this.loginService = loginService;
}
#RequestMapping(value = "/requestpayment", method = RequestMethod.POST, headers="Accept=application/json")
#ResponseBody
public ResponseEntity<PaymentResult> handleRequestPayment(#RequestBody PaymentRequest paymentRequest, HttpServletRequest request, HttpServletResponse response, BindingResult result) throws Exception{
ResponseEntity<PaymentResult> responseEntity = null;
new LoginValidator().validate(paymentRequest, result);
boolean valid = loginService.isLoginValid(paymentRequest, result);
if (valid){
responseEntity = setValidResponse(paymentRequest);
}else {
throw new TumsException("exception message");
}
return responseEntity;
}
private ResponseEntity<PaymentResult> setValidResponse(PaymentRequest paymentRequest){
PaymentResult paymentResult = paymentService.getResults(paymentRequest);
return new ResponseEntity<PaymentResult>(paymentResult, HttpStatus.OK);
}
}
And here's my test code:
public class RequestPaymentControllerTest {
PaymentService mockPaymentService;
RequestPaymentController requestPaymentController;
HttpServletRequest mockHttpServletRequest;
HttpServletResponse mockHttpServletResponse;
PaymentRequest mockPaymentRequest;
BindingResult mockBindingResult;
LoginService mockLoginService;
PaymentResult mockPaymentResult;
MockMvc mockMvc;
#Before
public void setUp() throws Exception {
mockPaymentService = createMock(PaymentService.class);
mockHttpServletRequest = createMock(HttpServletRequest.class);
mockHttpServletResponse = createMock(HttpServletResponse.class);
mockPaymentRequest = createMock(PaymentRequest.class);
requestPaymentController = new RequestPaymentController();
mockBindingResult = createMock(BindingResult.class);
mockLoginService = createMock(LoginService.class);
requestPaymentController.setPaymentService(mockPaymentService);
mockPaymentResult = createMock(PaymentResult.class);
mockMvc = MockMvcBuilders.standaloneSetup(new RequestPaymentController()).build();
}
#After
public void tearDown() throws Exception {
mockPaymentService = null;
mockHttpServletRequest = null;
mockHttpServletResponse = null;
mockPaymentRequest = null;
requestPaymentController = null;
mockBindingResult = null;
mockLoginService = null;
mockPaymentResult = null;
mockMvc = null;
}
#Test
public void testHandleRequestPayment() throws Exception{
initializeStateForHandleRequestPayment();
createExpectationsForHandleRequestPayment();
replayAndVerifyExpectationsForHandleRequestPayment();
}
private void initializeStateForHandleRequestPayment(){
}
private void createExpectationsForHandleRequestPayment(){
mockPaymentRequest.getServiceUsername();
expectLastCall().andReturn("testuser");
mockPaymentRequest.getServicePassword();
expectLastCall().andReturn("password1!");
mockLoginService.isLoginValid(mockPaymentRequest,mockBindingResult);
expectLastCall().andReturn(true);
mockPaymentService.getResults(mockPaymentRequest);
expectLastCall().andReturn(mockPaymentResult);
}
private void replayAndVerifyExpectationsForHandleRequestPayment() throws Exception{
replay(mockPaymentService, mockBindingResult, mockHttpServletRequest, mockHttpServletResponse, mockPaymentRequest, mockLoginService);
requestPaymentController.setLoginService(mockLoginService);
requestPaymentController.handleRequestPayment(mockPaymentRequest, mockHttpServletRequest, mockHttpServletResponse, mockBindingResult);
mockMvc.perform(post("/requestpayment")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isBadRequest());
verify(mockPaymentService, mockBindingResult, mockHttpServletRequest, mockHttpServletResponse, mockPaymentRequest, mockLoginService);
}
}
The results of the andDo(print()) are:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /requestpayment
Parameters = {}
Headers = {Content-Type=[application/json], Accept=[application/json]}
Handler:
Type = portal.echecks.controller.RequestPaymentController
Method = public org.springframework.http.ResponseEntity<portal.echecks.model.PaymentResult> portal.echecks.controller.RequestPaymentController.handleRequestPayment(portal.echecks.model.PaymentRequest,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,org.springframework.validation.BindingResult) throws java.lang.Exception
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotReadableException
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
MockHttpServletResponse:
Status = 400
Error message = null
Headers = {}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Process finished with exit code 0
As you can see, the test passes when I'm expecting a bad request status, but I've put in logging and I know that the ResponseBody I'm sending back has a 200 status. Like I said, this is my first time with MockMvc, so I assume I've not set something up right. Any suggestions?
An HttpMessageNotReadableException is
Thrown by HttpMessageConverter implementations when the read method
fails.
You also get a 400 Bad Request in your response. This should all tell you that you are not sending what your server is expecting. What is your server expecting?
#RequestMapping(value = "/requestpayment", method = RequestMethod.POST, headers="Accept=application/json")
#ResponseBody
public ResponseEntity<PaymentResult> handleRequestPayment(#RequestBody PaymentRequest paymentRequest, HttpServletRequest request, HttpServletResponse response, BindingResult result) throws Exception{
The main thing here is the #RequestBody annotated parameter. So you are telling your server to try and deserialize a PaymentRequest instance from the body of the HTTP POST request.
So let's see the request you are making
mockMvc.perform(post("/requestpayment")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON))
.andDo(print())
.andExpect(status().isBadRequest());
I don't see you providing a body to the request. There should be a content(String) call somewhere in there to set the content of the POST request. This content should be a JSON serialization of a PaymentRequest.
Note that because you are using the StandaloneMockMvcBuilder, you might need to set the HttpMessageConverter instances yourself, ie. a MappingJackson2HttpMessageConverter to serialize and deserialize JSON.
Note that the BindingResult parameter should come immediately after the parameter to which it's related. Like so
#RequestMapping(value = "/requestpayment", method = RequestMethod.POST, headers="Accept=application/json")
#ResponseBody
public ResponseEntity<PaymentResult> handleRequestPayment(#Valid #RequestBody PaymentRequest paymentRequest, BindingResult result, HttpServletRequest request, HttpServletResponse response) throws Exception{
Don't forget the #Valid.
Note that this
requestPaymentController.setLoginService(mockLoginService);
requestPaymentController.handleRequestPayment(mockPaymentRequest, mockHttpServletRequest, mockHttpServletResponse, mockBindingResult);
is completely unrelated to the MockMvc test you are doing.
In my case, as sprint mvc w/ jackson (jackson-mapper-asl, v-1.9.10) deserialization requires JSON parser. And jackson requires a default constructor for http request message deserialization, if there's no default constructor, jackson will have a problem w/ reflection and throws HttpMessageNotReadableException exception.
This is to say, all the classes/sub-classes which used as Request body, (in this case) requires a default constructor. This costed me a few moments after I tried adding custom converter and other suggestions I got in stackoverflow in vain.
Or you can add Custom Deserializer or Mixin annotation to avoid adding default constructor hierachically everywhere. as described here: http://blogs.jbisht.com/blogs/2016/09/12/Deserialize-json-with-Java-parameterized-constructor. Check this if you're interested.
Seems duplicated here > Spring HttpMessageNotReadableException.
Make sure of the following:
return object implements Serializable
#ResponseBody annotation used on the controller method
On your unit test
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {....})
#WebMvcTest
#AutoConfigureMockMvc
Probably too late to answer but just in case someone is still looking at this page.
As #Sotirios Delimanolis mentions, the problem is due to a bad request - a '#RequestBody' is specified in the parameter but never supplied in the request body. So, if you add that to request using 'content(someRequestString)' as below, it should work.
PaymentRequest paymentRequest = new PaymentRequest(...);
String requestBody = new ObjectMapper().valueToTree(paymentRequest).toString();
mockMvc.perform(post("/requestpayment")
.content(requestBody)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status").value("SUCCESS"))
.andExpect(jsonPath("$.paymentAmount", is(20)));
jsonPath may be used to verify the attributes on the response. In the above example, say PaymentResponse has attributes status and paymentAmount in the json response. These parts can be verified easily.
You may run into errors like -
NoClassDefFoundError: com/jayway/jsonpath/Predicate
while using jsonPath. So, make sure it is added to classpath explicitly as it is an optional dependency in spring-test and will not be available transitively. If using maven, do this:
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
<scope>test</scope>
</dependency>