Spring WebFluxTest weird behaviour when using Flux<String> as response - spring

I'm currently facing a weird bahaviour when testing the following controller using Spring WebFlux (v. 5.2.6)
#RestController
#RequestMapping(path = "/address", produces = MediaType.APPLICATION_JSON_VALUE)
public class AddressController {
#GetMapping(path = "/postcode")
public Flux<String> listPostCodes(...) {
return Flux.just("4711", "4712");
}
#GetMapping(path = "/cities")
public Flux<City> listCities() {
return Flux.just(new City("foo"), new City("bar"));
}
}
This controller is embedded within a "hello world" Spring-Boot application using spring-webflux-starter and a simple main class. Class City has just one property "name".
Now I have the following test (Junit5) to ensure response from the above mentioned controller
#SpringWebFluxTest
public AddressControllerTest {
#Test
public void postcodes() {
webTestClient.get()
.uri("/address/postcode")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$")
.isArray()
.jsonPath("$[0]")
.isEqualTo("4711")
}
#Test
public void cities() {
webTestClient.get()
.uri("/address/cities")
.exchange()
.expectStatus()
.isOk()
.expectBody()
.jsonPath("$")
.isArray()
.jsonPath("$[0].name")
.isEqualTo("foo")
}
}
You'd expected both test to pass? Me too. Unfortunately the first fails, telling me that the response body root isn't a json array, but a Long:
Expected: an instance of java.util.List
but: <47114712L> is a java.lang.Long
Why's that? Both responses are Flux, so I'd expect both response bodies to be an array, but only if the elements aren't "simple" types this seems to work in test. If I use postman to assert that behavior everything works exactly as expected, so I'd assume a testing problem somehow.
Could somebody explain that to me or might have a solution to this?
Thanks in advance

It's not an issue with the tests, the actual behaviour isn't what you expect.
Both responses are Flux, so I'd expect both response bodies to be an array
A Flux is not analogous to a list. It's a stream of data, which can be output verbatim, or collected into some other data structure when it's complete (a list being one example.)
In this case of course, the content type specified indicates you want that Flux to be collected into a list where possible - but this isn't universally the case. With POJOs, collections & arrays, Jackson will serialise them, and output an array. But here we're using raw strings, not using a JSON serialiser for those strings, so it's just concatenating & outputting them raw as they appear. (The concatenated postcodes just so happen to be all digits of course, hence why you then get an error about a long value.)
If you want a JSON array, you'll need to use:
#GetMapping(path = "/postcode")
public Mono<List<String>> listPostCodes() {
return Flux.just("4711", "4712").collectList();
}
To dispel a common myth with this approach ahead of time - you're not blocking by using collectList(), and you're not losing anything underneath the covers (since even if you use a Flux, the framework will still need to collect to a list internally to return the JSON array.)

Related

Using Quarkus Cache with Reactive and Mutiny correctly

I'm trying to migrate my project to Quarkus Reactive with Hibernate Reactive Panache and I'm not sure how to deal with caching.
My original method looked like this
#Transactional
#CacheResult(cacheName = "subject-cache")
public Subject getSubject(#CacheKey String subjectId) throws Exception {
return subjectRepository.findByIdentifier(subjectId);
}
The Subject is loaded from the cache, if available, by the cache key "subjectId".
Migrating to Mutiny would look like this
#CacheResult(cacheName = "subject-cache")
public Uni<Subject> getSubject(#CacheKey String subjectId) {
return subjectRepository.findByIdentifier(subjectId);
}
However, it can't be right to store the Uni object in the cache.
There is also the option to inject the cache as a bean, however, the fallback function does not support to return an Uni:
#Inject
#CacheName("subject-cache")
Cache cache;
//does not work, cache.get function requires return type Subject, not Uni<Subject>
public Uni<Subject> getSubject(String subjectId) {
return cache.get(subjectId, s -> subjectRepository.findByIdentifier(subjectId));
}
//This works, needs blocking call to repo, to return response wrapped in new Uni
public Uni<Subject> getSubject(String subjectId) {
return cache.get(subjectId, s -> subjectRepository.findByIdentifier(subjectId).await().indefinitely());
}
Can the #CacheResult annotations be used with Uni / Multi and everything is handled under the hood correctly?
Your example with a #CacheResult on a method that returns Uni should actually work. The implementation will automatically "strip" the Uni type and only store the Subject in the cache.
The problem with caching Unis is that depending on how this Uni is created, multiple subscriptions can trigger some code multiple times. To avoid this you have to memoize the Uni like this:
#CacheResult(cacheName = "subject-cache")
public Uni<Subject> getSubject(#CacheKey String subjectId) {
return subjectRepository.findByIdentifier(subjectId)
.memoize().indefinitely();
}
This will ensure that every subscription to the cached Uni will always return the same value (item or failure) without re-executing anything of the original Uni flow.

How to generate a random string and push it out using Mono to be displayed in a browser every X seconds or with random delay through Spring Reactive?

I would like the browser to display the randomly generated string on the browser as soon as the API generates it using Spring Reactive Mono.
Following is my sample program that works, generates random string and displays it on the browser every second but the page does not load any new data and it kind of looks like static data loading.
#RestController
public class LogTailerController {
#GetMapping(path = "/", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Mono<String> feed() {
return Mono.just("foo-" + Math.random()).delayElement(Duration.ofSeconds(1));
}
}
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
You may want to look into ConnectableFlux, which is similar to Flux, but is specifically designed to continuously emit elements. You can create a WebClient object, which produces a Mono via its exchange method by default. Then, simply refer to the route you created in your LogRailerController class to call the feed method.
public void connectFeed() {
ConnectableFlux<String> printEverySecond = WebClient.create("/") // Since your route is "/"
.post()
.body(...)
.exchange() // produces a Mono object
.flatMap(response -> response.bodyToMono(String.class)) // transformed into a Mono<String>
.flux() // now a Flux<String>
.replay(Duration.of(1, ChronoUnit.SECONDS))
.publish(); // now a ConnectableFlux<String>
printEverySecond.subscribe();
printEverySecond.connect();
}
Instead of using post().getBody()...flatMap(...), you could also just use get(), and call .bodyToMono(String.class) right after .exchange.
Doing this, you even place your feed() logic in the flatMap. The main issue with this strategy, and when using #RestController too, is that the request will eventually time out, which is kind of tricky get around using RxNetty. With that said, I'd recommend having a separate component class that calls ClientClass.printEverySecond() when it returns after 10 replays, or every 10 seconds, or whichever way you think best. The advantage of this strategy over using a #RestController is precisely that it can be called from another class the same way you would call any other bean method.
Note that all the topics here are within the scope of starter webflux dependency - I don't think you would need any others.

How can I return different types of objects from service in Spring boot?

I have the bellow service, its return type is either bytes or AmazonServiceException. The ResponseEntity in controller is formed based on these return types from service.
How can I:
1. design the service to return different types
2. make the controller to form ResponseEntiry based on its receives
#Service
Myservice
{
public x example(String a) {
try {
...
return bytes;
}catch(AmazonServiceException | IOException awsEx) {
return awsEx;
}
}
}
Here is the controller
#RequestMapping(method = GET, value = "/getData")
public ResponseEntity<?> getData(#RequestParam("a") String a) throws IOException {
return this.Myservice.example(a)condition ? returnA : returnB;
}
Generally speaking, returning multiple possible types is not a recommended thing to do. It is considered as a bad smell (like Object return type). Method should be specific about the return type it guarantees.
In this particular case, you may want to return String containing either raw bytes or the mentioned Exception message.
But what would be the best practice? Answer is: #ExceptionHandler and json response.
I posted a similar answer some time ago, I suggest you to read it: How can I modify default Json error response Spring?
Also, making a controller/service to return Exception is a really bad smell. I assume you are writing some kind of REST API, in this case your protocol of data exchange should be JSON. So, if anything bad has happened with your AWS service, then throw an exception and return an appropriate message.

Spring REST #RequestBody consume (XML or JSON) to POJO without annotations

I am writing a Springboot REST endpoint and want to consume XML or JSON requests for a simple service. In either case I want Spring to construct an #RequestBody pojo WITHOUT annotating any of the POJO. Is this OK? Safe? Performant?
I was reading this which told me about configuration by exception. To me this means if I structure my request to contain the exact name and case as the POJO member variables I want to populate the #RequestBody will be able to create my class SomeRequest.
If this is my REST endpoint:
#RequestMapping(value = GET_FOR_SOMETHING, method = RequestMethod.POST,
consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE},,
produces = {MediaType.APPLICATION_JSON_VALUE})
public #ResponseBody
StatusResponse<Boolean> getMpdForReqest(#RequestBody SomeRequest request)
And this is my POJO:
public class SomeRequest {
String one;
String two;
public String getOne() {
return one;
}
public void setOne(String one) {
this.one = one;
}
public String getTwo() {
return two;
}
public void setTwo(String two) {
this.two = two;
}
}
My JSON request:
{
"one": "str",
"two": "str"
}
My XML request:
<SomeRequest>
<one>str</one>
<two>str</two>
</SomeRequest>
My question is: why should I not do this or is it perfectly fine?
Thank you all.
TLDR; It is perfectly fine.
Is this OK? Safe? Performant?
Yes, it is as performant as it's annotated cousin, if you take program efficiency into account.
If you take the Programmer efficiency into account, it is much more efficient as the developer doesn't have to deal with a bunch of annotations.
Speaking of Programmer efficiency, I would encourage you to use project Lombok instead of crapping your POJO with bunch of getter and setter methods, that's what cool kids do now a days.
Catch
This will work fine as long as your json fields are one word and small case.
When you have multi-word field name, Java standard is the camelCase and usually JSON standard is the snake_case. In this case, you can either have a Class level Annotation (one per class, so not much ugly). Or, since you are using spring boot, you can use an application wide property (spring.jackson.property-naming-strategy = SNAKE_CASE ).
If you have weird json field names with spaces in between, you might need to use #JsonProperty annotation. Remember, this is a perfectly valid json
{
"just a name with a space" : "123"
}
POJO as RequestBody works perfectly fine. Just note that Spring however will return 400 - Bad Request for every request that can not be mapped to the #RequestBody annoted object.

Looking for Simple Way To Unit Test Spring-WS Endpoint Methods

As the topic states, I am looking for a simple was to test ONLY the endpoint mapping urls for a Spring-WS service. For example, here is a sample method:
#Endpoint
public class ServiceClass {
private static final String NAMESPACE_URI = "http://mysite.com/thisApp/schemas";
#PayloadRood(namespace = NAMESPACE_URI, localPart="Method1")
#ResponsePayload
public ResponseObject method1(#RequestPayload RequestObject request) {
// do something
// return the ResponseObject
}
}
All I want to do is verify that a call to http://mysite.com/thisApp/schemas{serviceName actually hits a method; I don't care (yet) whether there is a response. In other words, I need to ensure that a NoEndpointFoundException is not returned.
I know Spring-WS has mock servers and clients, but I'm not quite seeing what I'm looking for.
Thanks for any help.
You can see if rest-assured maybe helpful for your unit test.

Resources