I am trying to test a method that posts an object to the database using Spring's MockMVC framework. I've constructed the test as follows:
#Test
public void testInsertObject() throws Exception {
String url = BASE_URL + "/object";
ObjectBean anObject = new ObjectBean();
anObject.setObjectId("33");
anObject.setUserId("4268321");
//... more
Gson gson = new Gson();
String json = gson.toJson(anObject);
MvcResult result = this.mockMvc.perform(
post(url)
.contentType(MediaType.APPLICATION_JSON)
.content(json))
.andExpect(status().isOk())
.andReturn();
}
The method I'm testing uses Spring's #RequestBody to receive the ObjectBean, but the test always returns a 400 error.
#ResponseBody
#RequestMapping( consumes="application/json",
produces="application/json",
method=RequestMethod.POST,
value="/object")
public ObjectResponse insertObject(#RequestBody ObjectBean bean){
this.photonetService.insertObject(bean);
ObjectResponse response = new ObjectResponse();
response.setObject(bean);
return response;
}
The json created by gson in the test:
{
"objectId":"33",
"userId":"4268321",
//... many more
}
The ObjectBean class
public class ObjectBean {
private String objectId;
private String userId;
//... many more
public String getObjectId() {
return objectId;
}
public void setObjectId(String objectId) {
this.objectId = objectId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
//... many more
}
So my question is: how to I test this method using Spring MockMVC?
Use this one
public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));
#Test
public void testInsertObject() throws Exception {
String url = BASE_URL + "/object";
ObjectBean anObject = new ObjectBean();
anObject.setObjectId("33");
anObject.setUserId("4268321");
//... more
ObjectMapper mapper = new ObjectMapper();
mapper.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter();
String requestJson=ow.writeValueAsString(anObject );
mockMvc.perform(post(url).contentType(APPLICATION_JSON_UTF8)
.content(requestJson))
.andExpect(status().isOk());
}
As described in the comments, this works because the object is converted to json and passed as the request body. Additionally, the contentType is defined as Json (APPLICATION_JSON_UTF8).
More info on the HTTP request body structure
the following works for me,
mockMvc.perform(
MockMvcRequestBuilders.post("/api/test/url")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(createItemForm)))
.andExpect(status().isCreated());
public static String asJsonString(final Object obj) {
try {
return new ObjectMapper().writeValueAsString(obj);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
The issue is that you are serializing your bean with a custom Gson object while the application is attempting to deserialize your JSON with a Jackson ObjectMapper (within MappingJackson2HttpMessageConverter).
If you open up your server logs, you should see something like
Exception in thread "main" com.fasterxml.jackson.databind.exc.InvalidFormatException: Can not construct instance of java.util.Date from String value '2013-34-10-10:34:31': not a valid representation (error: Failed to parse Date value '2013-34-10-10:34:31': Can not parse date "2013-34-10-10:34:31": not compatible with any of standard forms ("yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", "EEE, dd MMM yyyy HH:mm:ss zzz", "yyyy-MM-dd"))
at [Source: java.io.StringReader#baea1ed; line: 1, column: 20] (through reference chain: com.spring.Bean["publicationDate"])
among other stack traces.
One solution is to set your Gson date format to one of the above (in the stacktrace).
The alternative is to register your own MappingJackson2HttpMessageConverter by configuring your own ObjectMapper to have the same date format as your Gson.
I have encountered a similar problem with a more recent version of Spring. I tried to use a new ObjectMapper().writeValueAsString(...) but it would not work in my case.
I actually had a String in a JSON format, but I feel like it is literally transforming the toString() method of every field into JSON. In my case, a date LocalDate field would end up as:
"date":{"year":2021,"month":"JANUARY","monthValue":1,"dayOfMonth":1,"chronology":{"id":"ISO","calendarType":"iso8601"},"dayOfWeek":"FRIDAY","leapYear":false,"dayOfYear":1,"era":"CE"}
which is not the best date format to send in a request ...
In the end, the simplest solution in my case is to use the Spring ObjectMapper. Its behaviour is better since it uses Jackson to build your JSON with complex types.
#Autowired
private ObjectMapper objectMapper;
and I simply used it in my test:
mockMvc.perform(post("/api/")
.content(objectMapper.writeValueAsString(...))
.contentType(MediaType.APPLICATION_JSON)
);
Related
I've RESTful service Spring MVC based.
The service has a RESTful resource method that returns the following response:
public class OperationalDataResponse<T> {
private String status;
private String statusMessage;
private T result;
//getters and setters
}
This response object encapsulates the result object of type T.
On the client side I use RestTemplate with GsonHttpMessageConverter added.
I get the response from service as a ResponseEntity
I handle the generic response with runtime Type as below:
public class OperationalDataRestClient<REQ,RESULT_TYPE> {
public OperationalDataResponse<RESULT_TYPE> getOperationalData(String resourcePath, Map<String, Object> urlVariables, Class<RESULT_TYPE> resultType) {
//code to invoke service and get data goes here
String responseString = responseEntity.getBody();
response = GsonHelper.getInstance().fromJson(responseString, getType(OperationalDataResponse.class, resultType));
}
Type getType(final Class<?> rawClass, final Class<?> parameter) {
return new ParameterizedType() {
#Override
public Type[] getActualTypeArguments() {
return new Type[] { parameter };
}
#Override
public Type getRawType() {
return rawClass;
}
#Override
public Type getOwnerType() {
return null;
}
};
}
}
This works like a charm as long as my resultType is a non-collection class.
So, this works great from caller code:
getOperationalData(someResourcePath, someUrlVariables, MyNonGenericClass.class)
However if my resultType is a collection (say, List<String> or List<MyNonGenericClass>)
then I don't know how to pass the resultType Class from the caller code.
For example, from caller code,
getOperationalData(someResourcePath, someUrlVariables, List.class)
or
getOperationalData(someResourcePath, someUrlVariables, List<MyNonGenericClass>.class)
throws compilation error.
I tried passing on ArrayList.class as well but that too doesn't work.
Any suggestion how can I pass a generic collection as a resultType from caller code (in other words, as an example, how can I pass the class object of a List<String> or List<MyNonGenericClass> from caller code ?)
If you know that ResultType is coming as a List, Then it will obvious fail like you said compilation issue.Why? because you are trying to send a List when you method only accepts a single value.In order to over come that issue you will have to change the method arguments to the following
public OperationalDataResponse<RESULT_TYPE> getOperationalData(String resourcePath, Map<String, Object> urlVariables, List<Class<RESULT_TYPE>> resultType){
....
}
and you will have to make some slight modification to getType() Method,loop it and then pass each class value to getType method like so
for(MyNonGenericClass myClass:mylist){
getType(OperationalDataResponse.class, myClass.getClass());
}
As part of Spring Data, there is DomainClassConverter which helps with repository lookups so that we don't have to do lookup manually.
http://docs.spring.io/spring-data/commons/docs/current/reference/html/#core.web.basic.domain-class-converter
#Controller
#RequestMapping("/orders/{id}")
public class PaymentController {
#RequestMapping(path = "/payment", method = PUT)
ResponseEntity<?> submitPayment(#PathVariable("id") Order order) {
if (order == null) {
throw new OrderNotFoundException(???orderId???);
}
...
}
}
How can I get to {id} from request without changing Order parameter to Long?
DomainClassConverter converts {id} to null as expected
My intention is to use order id in OrderNotFoundException
I can think of couple of ways.
1. Change the method to
#RequestMapping(path = "/payment", method = PUT)
ResponseEntity<?> submitPayment(#PathVariable("id") Order order, HttpServletRequest request) {
String path = request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString()
// path will have "/payment/id". use substring or something similar to get just id
}
Write a #Around Aspect for #RequestMapping and get the argument
#Aspect
#Configuration
public class ControllerAspect {
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMapping() {}
#Around("requestMapping()")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
String path = (String) joinPoint.getArgs()[0];
Object result= joinPoint.proceed();
}
}
In a spring boot project, I'd like to test my ErrorController with Junit.
The code is as the following snippet.
#RestController
public class ApiErrorController implements ErrorController {
private static final Logger LOGGER = LoggerFactory.getLogger(ApiErrorController.class);
#Value("${server.error.path}")
private String errorPath;
#Override
public String getErrorPath() {
return this.errorPath;
}
#RequestMapping("/error")
public ResponseEntity<ErrorResult> error(HttpServletRequest request, HttpServletResponse response) {
String requestURI = (String) request.getAttribute("javax.servlet.forward.request_uri");
LOGGER.info("error handling start url = {}", requestURI);
String servletMessage = (String) request.getAttribute("javax.servlet.error.message");
Integer servletStatus = (Integer) request.getAttribute("javax.servlet.error.status_code");
String[] messages = new String[0];
if (!StringUtils.isNullOrEmpty(servletMessage)) {
messages = new String[] { servletMessage };
}
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
try {
if (servletStatus != null && servletStatus instanceof Integer) {
status = HttpStatus.valueOf(servletStatus);
}
} catch (Exception ex) { // test this exception
LOGGER.warn("http status not converted.{}", request.getAttribute("javax.servlet.error.status_code"), ex);
}
ErrorResult body = new ErrorResult();
body.setMessages(messages);
ResponseEntity<ErrorResult> responseResult = new ResponseEntity<>(body, status);
return responseResult;
}
}
When a business exception happened in my Controller(for example AbcController), then the program goes into the ExceptionControllerAdvice class.
If an exception happened in ExceptionControllerAdvice, then the program goes into the above ApiErrorController class.
Could someone tell me how to test the case that HttpStatus.valueOf(servletStatus) fail?
In addition, I want request.getAttribute("javax.servlet.error.message") return a non-empty string.
How to achieve what I'd like to test?
By the way, I don't want to only test the logic of error method. I'd like to use AbcController I mentioned to make the test. What I want is when a error happens in AbcController, then the error method in ApiErrorController can handle it successfully.
APPEND:
For example, ExceptionControllerAdvice will handle the business exception.
#ControllerAdvice(annotations = RestController.class)
public class ExceptionControllerAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionControllerAdvice.class);
#ExceptionHandler({ BusinessCloudException.class })
public ResponseEntity<ErrorResult> handleBlCloudException(HttpServletRequest request, HttpServletResponse response,
BlCloudException ex) {
HttpStatus status = ErrorUtils.toHttpStatus(ex.getType());
ErrorResult body = new ErrorResult();
body.setMessages(ex.getMessageArray());
ResponseEntity<ErrorResult> responseResult = new ResponseEntity<>(body, status);
return responseResult;
}
}
If there's an error happened in the handleBlCloudException method, then the program goes into ApiErrorController to handle this error.
How do the program produce the a specific servletStatus and javax.servlet.error.message? How to mock to do this?
First of all there is quite a lot going on in that error method. You might consider moving some of the logic to a specialized class / public methods.
Apart from that i would suggest using Mockito.
Fist of all create a method to encapsulate the HttpStatus retrieval:
HttpStatus getHttpStatusByServletStatus(Integer servletStatus){
return HttpStatus.valueOf(servletStatus);
}
and change your code to :
if (servletStatus != null && servletStatus instanceof Integer) {
status = getHttpStatusByServletStatus(servletStatus);
}
Then the test class:
public ApiErrorControllerTest{
#Spy
private ApiErrorController apiErrorController;
#Mock
HttpServletRequest requestMock;
#Mock
HttpServletResponse responseMock;
#Befire
public void init(){
MockitoAnnotations.initMocks(this);
}
#Test
public void test(){
// Arrange
HttpStatus expectedStatus = // expected status
String expectedErrorMessage = // ..
doReturn(expectedStatus).when(apiErrorController)
.getHttpStatusByServletStatus(Mockito.anyString());
when(requestMock.getAttribute("javax.servlet.error.message"))
.thenReturn(expectedErrorMessage);
// other setup..
// Act
apiErrorController.error(requestMock, responseMock);
// Assertions
}
Following is a request fullcalendar js send to the server.
http://localhost:8080/NVB/rest/calendar/events?start=1425168000&end=1428796800 400
How to specify Date pattern (#DateTimeFormat) in Spring Request Param to convert this time to a Date object. I tried different patterns but getting 405 Bad Request.
#RequestMapping(value = "/events", method = RequestMethod.GET)
public #ResponseBody List<EventDto> addOrder(#RequestParam(value = "start") #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date start,
#RequestParam(value = "end") #DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Date end) {
LOGGER.info("Requesting event from [{}] to [{}]", start, end);
return new LinkedList<EventDto>();
}
Since timestamps aren't a formatted date (going by Java's SimpleDateFormat options), but more a numeric value: I would recommend making a custom data-binder for Date objects if you're doing this more often than this single instance. See http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#portlet-ann-webdatabinder
As a one-off solution you can bind them to Long parameters and create your own Date object with new Date(start).
Using #InitBinder and WebDataBinder:
#RestController
public class SimpleController {
//... your handlers here...
#InitBinder
public void initBinder(final WebDataBinder webdataBinder) {
webdataBinder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
#Override
public void setAsText(String text) throws IllegalArgumentException {
setValue(new Date(Long.valueOf(text)));
}
});
}
}
I am storing objects in my database as JSON strings. I want to make a REST service that exposes these strings. When I write my methods however, the strings I get back have their quotes escaped. For example, I have included a method that returns a String,
#RequestMapping(value = "test", method = RequestMethod.GET)
public #ResponseBody
String getTest() {
return "{\"a\":1, \"b\":\"foo\"}";
}
but when I call this method in the browser I get a back "{\"a\":1, \"b\":\"foo\"}" when what I really want to happen is {"a": 1, "b": "foo"}. I think "String" as the return type is likely the problem, but what else can I do? A wrapper class does the same thing:
{
"value" : "{\"a\":1, \"b\":\"foo\"}"
}
I could serialize it and then return the object, but that seems a bit ridiculous.
Here is a possibly the relevant portion of my configuration file:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
super.configureMessageConverters(converters);
converters.add(mappingJacksonHttpMessageConverter());
}
#Bean
MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter() {
MappingJacksonHttpMessageConverter mappingJacksonHttpMessageConverter = new MappingJacksonHttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
mappingJacksonHttpMessageConverter.setObjectMapper(objectMapper);
mappingJacksonHttpMessageConverter.setPrettyPrint(true);
return mappingJacksonHttpMessageConverter;
}
Thanks
EDIT: as was suggested below, it seems the string is being double encoded. Commenting out the 2 classes in my configuration fixes this issue. However, I still have other places where I want to return Objects and would like to keep those running through that common serializing bean that I know where to configure. So I see my options as:
a) Do all the serializing myself. All methods return Strings, and those that are already JSON return themselves, and those that are objects all return JSONUtil.toJson(object). I don't love this approach, but I know it will work.
b) Use a wrapper class that looks kind of like:
public static class Wrapper {
#JsonRawValue
private final String value;
}
This leads to an awkward "value" at the front though that has no real meaning.
Basically what I want is #JsonRawValue, but to have it work on RequestMapping methods instead of properties.
Thoughts? Opinions? Other suggestions?
This works with Jackson 2 (at least):
#Controller
public class YourController {
#RequestMapping(..)
public #ResponseBody Json get() {
return new Json("{ \"attr\" : \"value\" }");
}
}
class Json {
private final String value;
public Json(String value) {
this.value = value;
}
#JsonValue
#JsonRawValue
public String value() {
return value;
}
}
Not particularly pretty but works. I only wish Spring supported this:
#RequestMapping(..)
public #JsonRawValue #ResponseBody String get() {
// ...
}
I guess what you want is producing a response with content-type application/json. In your case, when you have the json-data as a raw string, do the following:
In your controller add produces="application/json" to your #RequestMapping attribute:
#RequestMapping(value = "test", method = RequestMethod.GET, produces="application/json")
public #ResponseBody
String getTest() {
return "{\"a\":1, \"b\":\"foo\"}";
}
Then you have to configure the StringHttpMessageConverter to accept the application/json media-type.
With Java-config:
#Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(
Charset.forName("UTF-8"));
stringConverter.setSupportedMediaTypes(Arrays.asList( //
MediaType.TEXT_PLAIN, //
MediaType.TEXT_HTML, //
MediaType.APPLICATION_JSON));
converters.add(stringConverter);
}
With XML-config:
<bean class = "org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<array>
<bean class = "org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes" value="application/json; charset=UTF-8" />
</bean>
</array>
</property>
</bean>
I used this:
#RequestMapping(..)
#ResponseBody
public JsonNode myGetRequest(){
...
//rawJsonString is the raw Json that we want to proxy back to the client
return objectMapper.readTree(rawJsonString);
}
And the Jackson converter knew how to transform the JsonNode into plain Json.
If you want to convert JSON String to JSON object in your browser, keep string convertor before Jackson convertor.
Follow this link for complete example. It works with custom converter configuration plus spring validation.
It Works
converters.add(stringConverter());
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
It Doesn't
converters.add(mappingJackson2HttpMessageConverter());
converters.add(stringConverter());
super.configureMessageConverters(converters);
In my case, I wanted the response type to be determined by a request parameter, so had to specify the content type in the code, e.g.:
#RequestMapping("/myurl")
public void radiusSearch(#RequestParam responseType, HttpServletResponse response) throws IOException {
String jsonResponse = makeSomeJson();
response.setContentType(responseType);
try {
response.getOutputStream().write(jsonResponse.getBytes());
} finally {
response.getOutputStream().close();
}
}
Today we had the same issue and solved it with multiple converters. Now every String will treated as a string and every other Object will get serialised by Jackson. This allows to serialise manually (by returning String) or automatically (by returning something else) in Spring controllers.
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(stringConverter());
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
#Bean
public StringHttpMessageConverter stringConverter() {
final StringHttpMessageConverter stringConverter = new StringHttpMessageConverter(UTF_8);
stringConverter.setSupportedMediaTypes(Arrays.asList(
MediaType.TEXT_PLAIN,
MediaType.TEXT_HTML,
MediaType.APPLICATION_JSON));
return stringConverter;
}
#Bean
public GenericHttpMessageConverter<Object> mappingJackson2HttpMessageConverter() {
final ObjectMapper objectMapper = objectMapperBuilder().build();
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper);
return converter;
}
The \" means the character " is being escaped, which is standard. If it's being printed like that, you're probably double-serializing the object.
I know this is an old question, but I was just dealing with the opposite problem myself (I was returning a String and WANTED it to get converted to JSON). In your case, it sounds like you simply want to have your String treated as a plain string and not have any sort of JSON conversion done on it as you already have JSON.
So in your case you don't want to use the MappingJacksonHttpMessageConverter (or the MappingJackson2HttpMessageConverter if you're now using Jackson2). You want no conversions done at all, and that converter converts Java objects to/from JSON. So instead you should just use the plain StringHttpMessageConverter. You can do that by changing your setup method like this:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new StringHttpMessageConverter());
}
This converter is applicable to */* types (the document is incorrect which says text/*, I found out the hard way in the debugger). So whether your content type is application/json or not, either way Spring won't mess with your Strings if you use this converter.
The solution to your problem is, this works perfectly without changing any configurations
import com.fasterxml.jackson.databind.JsonNode;
import com.github.fge.jackson.JsonLoader;
JsonNode getTest() {
return JsonLoader.fromString("{\"a\":1, \"b\":\"foo\"}");
}