Problem with Kotlin inline class deserialization to JSON in tests - spring

I'm trying to write tests for routes in a WebFlux application, but I encountered a problem. I got an error during parsing to JSON:
13:38:20.601 [parallel-1] DEBUG org.springframework.web.server.handler.ResponseStatusExceptionHandler - [248c6c90] Resolved [ServerWebInputException: "400 BAD_REQUEST "Failed to read HTTP message"; nested exception is org.springframework.core.codec.DecodingException: JSON decoding error: Cannot construct instance of `com.test.GetItemRequest` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.test.GetItemRequest` (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)<EOL> at [Source: (org.springframework.core.io.buffer.DefaultDataBuffer$DefaultDataBufferInputStream); line: 1, column: 2]"] for HTTP POST /v1/download
I have a request that uses an inline class:
#JvmInline
value class ItemName(val rawValue: String)
data class GetItemRequest(val name: ItemName)
The test:
#Test
fun `Request is parsed successfully`() {
//...
val client = WebTestClient.bindToRouterFunction(router.eimApiRoutes()).build()
val request = """{"name":"item1"}"""
val resp = client
.post()
.uri(EimApiRouter.DOWNLOAD_PATH)
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.body(Mono.just(request), String::class.java)
.exchange()
.expectStatus()
.isOk
.expectBody()
.returnResult()
.responseBody
val expectedResponse = "OK"
assertEquals(expectedResponse, String(resp!!))
}
I can work the problem around by adding a default constructor:
data class GetItemRequest(val name: ItemName) {
constructor() : this(ItemName(""))
}
When I change the type of the parameter to String, it works.
The question:
Could somebody give the real solution? Maybe there is something missing in the test web client configuration (I tried to configure the Kotlin codecs manually, but without success)?

Related

spring tests with mocks and HttpMessageConversionException

i have something like that in my ParkingServiceController.
#PostMapping("/departure")
public ResponseEntity<String> departure(#RequestBody CarAtGateModel carAtGateModel) throws UnidentifiedCarException {
CarAndParkingEntity carAndParkingEntity = carsAndParkingsRepository.findByIdCar(
carAtGateModel.getCarEntity().getIdCar()).orElseThrow(() -> new UnidentifiedCarException());
carAndParkingEntity.setIdParking("-1");
carsAndParkingsRepository.flush();
return new ResponseEntity<>(responsesMessages.gateUp(), HttpStatus.OK);
}
and next i wanted to do test with some mocks.
#Test
public void testArrivalWhenParkingIdNotExists() {
//given
CarAndParkingEntity carAndParkingEntity = mock(CarAndParkingEntity.class);
carAtGateModel = mock(CarAtGateModel.class);
//when
when(carsAndParkingsRepository.findByIdCar(anyString())).thenReturn(Optional.of(carAndParkingEntity));
HttpEntity<CarAtGateModel> request = new HttpEntity<>(carAtGateModel);
ResponseEntity response = testRestTemplate.postForEntity("/departure", request, String.class);
//then
assertEquals("Parking with that id does not exists", response.getBody());
}
but i'm getting that exception every time with every code change in test
org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.mockito.internal.creation.bytebuddy.ByteBuddyCrossClassLoaderSerializationSupport and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.example.parkingservice.models.CarAtGateModel$MockitoMock$1316802841["mockitoInterceptor"]->org.mockito.internal.creation.bytebuddy.MockMethodInterceptor["serializationSupport"])
When i'm not using any mock test passess correctly so imo i'm doing something wrong with mocking
At first sight, as your ParkingServiceController has a composition with CarsAndParkingRepository, you should mock that dependency first. I don't know why you are mocking carAtGateModel, you can use a real object representing the the data you want to pass to the controller (the same applies to carAndParkingEntity).
It would be helpful if you add more details, explaining what you want to test because it's not totally clear the assertion you are doing.

How to change WebClient behaviour concerning LocalDateTime?

I am using Spring Boot 2.4.5 and try to request a REST API of another application using WebClient. I know that the other application provides the requested information as a Collection.
When I use an Object[] to receive the response:
Object[] ob = (Object[]) webClient
.get()
.uri(endpoint)
// .bodyValue(criteria)
.exchangeToMono(response -> {
if (response.statusCode()
.equals(HttpStatus.OK)) {
return response.bodyToMono(Object[].class);
} else if (response.statusCode()
.is4xxClientError()) {
return Mono.just("Error response");
} else {
return response.createException()
.flatMap(Mono::error);
}
}).block();
I can see that I receive a LinkedHashMap with all the values including a field:
date_of_declaration -> 2020-03-02T08:43:10
However, if possible, I want to let WebClient immediately convert the response into the designated DTOs...
DeclarationDTO[] ob = (DeclarationDTO[]) webClient
.get()
.uri(endpoint)
// .bodyValue(criteria)
.exchangeToMono(response -> {
if (response.statusCode()
.equals(HttpStatus.OK)) {
return response.bodyToMono(DeclarationDTO[].class);
} else if (response.statusCode()
.is4xxClientError()) {
return Mono.just("Error response");
} else {
return response.createException()
.flatMap(Mono::error);
}
}).block();
...I get an exception when a LocalDateTime object shall be deserialized.
org.springframework.core.codec.DecodingException: JSON decoding error: Cannot deserialize value of type `java.time.LocalDateTime` from String "02-03-2020 01:20:00": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '02-03-2020 01:20:00' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {HourOfAmPm=1, NanoOfSecond=0, SecondOfMinute=0, MicroOfSecond=0, MinuteOfHour=20, MilliOfSecond=0},ISO resolved to 2020-03-02 of type java.time.format.Parsed; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.LocalDateTime` from String "02-03-2020 01:20:00": Failed to deserialize java.time.LocalDateTime: (java.time.format.DateTimeParseException) Text '02-03-2020 01:20:00' could not be parsed: Unable to obtain LocalDateTime from TemporalAccessor: {HourOfAmPm=1, NanoOfSecond=0, SecondOfMinute=0, MicroOfSecond=0, MinuteOfHour=20, MilliOfSecond=0},ISO resolved to 2020-03-02 of type java.time.format.Parsed
at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 1438] (through reference chain: java.lang.Object[][0]->de.xxx.myportal.api.infrastructure.dto.MyDTO["a_person"]->de.xxx.myportal.api.infrastructure.dto.APersonDTO["foobar"])
I think WebClient has an internal ObjectMapper, so maybe it is possible to modify this ObjectMapper during WebClient instantiation? ...or is there a better way to tell Webclient how to handle LocalDateTime? ...maybe a WebClient customized by a configuration or...?
I can't explain but after removing any JsonFormat annotation it works out of the box. (Weird!)
This may help https://www.baeldung.com/spring-boot-formatting-json-dates
In your DeclarationDTO you can use something like:
#JsonFormat(pattern="yyyy-MM-dd'T'HH:mm:ss")
#JsonProperty("date_of_declaration")
private LocalDateTime dateOfDeclaration;

Cannot deserialize instance of `com.example.million.model.Domain` out of START_ARRAY token

I'm using Kotlin, Spring Boot, Jackson dataformat csv. Not sure, how I can return response from my csv as List of domain objects. and I have the following error
Cannot deserialize instance of com.example.million.model.Domain out of START_ARRAY token
My code is the following:
#Service
class DomainService {
fun getDomains(): List<Domain> {
val mapper = CsvMapper()
mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY)
val csvFile = File("myCsv.csv")
val response: List<Domain> = mapper.readerFor(Domain::class.java).readValues<Domain>(csvFile).readAll()
return response
}
}
data class Domains(var domain: String){}
Deleting this line, as otherwise you are wrapping each csv line in an array, which leads to your error message.
mapper.enable(CsvParser.Feature.WRAP_AS_ARRAY)
Your code otherwise looks fine.

Spring WebFlux throws 'producer' type is unknow when I return value in the response body

I'm using Spring Boot with Kotlin, and now trying to get status value from a GET restful service by passing a handler for a reactive service.
I can see that the handler I'm passing is in the request, but whenever I'm building the body, I get this exception:
java.lang.IllegalArgumentException: 'producer' type is unknown to ReactiveAdapterRegistry
at org.springframework.util.Assert.notNull(Assert.java:198) ~[spring-core-5.2.0.RELEASE.jar:5.2.0.RELEASE]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException
Here is my code:
#Bean
fun getReceiptConversionStatus() = router {
accept(MediaType.APPLICATION_JSON).nest {
GET("/BsGetStatus/{handler}", ::handleGetStatusRequest)
}
}
private fun handleGetStatusRequest(serverRequest: ServerRequest): Mono<ServerResponse> = ServerResponse
.ok()
.contentType(MediaType.APPLICATION_JSON)
.body(GetStatusViewmodel(fromObject(serverRequest.pathVariable("handler"))), GetStatusViewmodel::class.java)
.switchIfEmpty(ServerResponse.notFound().build())
and that's my Viewmodel:
data class GetStatusViewmodel(
#JsonProperty("handler") val documentHandler: String
)
Flux and Monos are Producers. They produce stuff. You are not passing in a producer in the body thats why you get the error, it doesn't recognize the producer you are passing, because you are passing in a GetStatusViewmodel.
Your body needs to be of type Mono<GetStatusViewmodel>. You can either replace body with bodyValue (it will automatically wrap it for you) or you can wrap your GetStatusViewodel in a Mono using Mono#just before passing it into the body function.
For me, I was doing something like this:
webClient.post()
.uri("/some/endpoint")
.body(postRequestObj, PostRequest.class) // erroneous line
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(PostResponse.class)
.timeout(Duration.ofMillis(5000))
When looking at the springs docs for that function body(), this is what's explained:
Variant of body(Publisher, Class) that allows using any producer that can be resolved to Publisher via ReactiveAdapterRegistry.
Parameters:
producer - the producer to write to the request
elementClass - the type of elements produced
Returns:
this builder
So the first parameter can't just be any object, it has to be a producer. Changing my above code to wrap my object around in a Mono fixed this issue for me.
webClient.post()
.uri("/some/endpoint")
.body(Mono.just(postRequestObj), PostRequest.class)
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(PostResponse.class)
.timeout(Duration.ofMillis(5000))
reference: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.RequestBodySpec.html
I actually solved it, and I will post it here just in case somebody would do the same mistake I did :( It was a typical mistake for those who work with Java, it was a wrong import.
I was using fromObject() method in my application "I updated the question to match my actual code". You can find this function in both of these imports, and I was using one of the overloaded body() functions to pass this wrong placed function:
//this is the wrong import I was using
import org.springframework.web.reactive.function.server.EntityResponse.fromObject
//this is the correct one for building the mono body
import org.springframework.web.reactive.function.BodyInserters.fromObject
By using the method from BodyInserters, you will be able to pass fromObject(T) to the body method and it will return the mono result.
The specified code resolved the issue
public Mono<ServerResponse> getName(ServerRequest request) {
return ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(birthday);
}

SpringBoot capture requestBody on error

I have an app that is accepting some JSON. It has a controller with #RestController and then a
#RequestMapping(value="/junk", method=RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE)
public MyResponse postInfo(#RequestBody MyRequest info) {
...
}
This "works" in that it accepts JSON that is valid as per the "MyRequest" object and handles it. I am interested in capturing the times when the JSON is not valid.
Currently what happens when there's bad data is Spring seems to write something like this to stdout:
2017-04-26 13:57:40.621 WARN 13537 --- [pr-8080-exec-13] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Unexpected character ('}' (code 125)): was expecting double-quote to start field name
at [Source: java.io.PushbackInputStream#cc73b4; line: 1, column: 1048800]
at [Source: java.io.PushbackInputStream#cc73b4; line: 1, column: 1048796] (through reference chain: com.junk.ProcessInfo["process_list"]->java.util.ArrayList[4692]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Unexpected character ('}' (code 125)): was expecting double-quote to start field name
at [Source: java.io.PushbackInputStream#cc73b4; line: 1, column: 1048800]
at [Source: java.io.PushbackInputStream#cc73b4; line: 1, column: 1048796] (through reference chain: com.junk.ProcessInfo["process_list"]->java.util.ArrayList[4692])
This is all well and good, but I want to see what the request was that was bad so I can go fix it on the source side.
I was playing with writing a "ControllerAdvisor" like
#ControllerAdvice(annotations = RestController.class)
public class ControllerAdvisor {
private static final Logger logger = LogManager.getLogger();
#ModelAttribute
public void logBody(HttpServletRequest request, HttpServletResponse response, #RequestBody String requestString) {
logger.trace("requestString" + requestString);
}
}
which seems to write out the request body of every request (not just errors) but when I use this, it seems to modify the request such that the actual controller will fail with
2017-04-26 14:28:31.432 WARN 523 --- [io-8081-exec-29] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public com.junk.MyResponse com.junk.ProcessController.postInfo(com.junk.MyRequest)
I was also playing with Actuator, but Actuator.trace doesn't actually give me the body.
Is there any way to get the actual requestBody inside my RestController?
Edit: Here's a complete app that demonstrates the behavior:
Download example
From what the log says it's not an issue with your server side implementation. From the log I interpret it as you posted a JSON body like this:
{
myPropert: myvalue
}
when the message converter is expecting:
{
"myPropert": "myvalue"
}

Resources