What is the best way to programmatically assert a WebFlux stream? - java

So let us say we have something like:
public class SomeService {
...
public Flux<String> getStringsFromWebServer() {
return webClient.get()
.uri(this::generateSomeUrl)
.retrieve()
.bodyToMono(SomePojo.class)
.map(SomePojo::getStringList)
.flatMapMany(Flux::fromIterable);
}
Does it make sense to write tests that look like this:
void getStringsFromWebServer_shouldParseInOrderOfReceivingStrings() {
// given
// I have mocked up a WebClient, that is wired up to a Mocked Web Server
// I am preloading the Mocked Web Server with this JSON
String jsonStrings = "{'stringList': ['hello1', 'hello2', 'hello3']}"
mockWebServer.enqueue(new MockResponse().setResponseCode(200))
.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.setBody(jsonStrings);
// when
Flux<String> result = someService.getStringsFromWebServer();
// then
StepVerifier.FirstStep<String> fluxStep = StepVerifier.create(result);
for (int i = 1; i < 4; i++) {
String expectedInput = String.format("hello%d", i);
fluxStep.assertNext(someString -> assertEquals(expectedInput, someString));
}
fluxStep.verifyComplete();
}
Is this how you are to programmatically assert the order that comes back out from Flux?
Am I doing something bad with the assertNext flux method? I mean in this sense, I am always providing ordered data so I am assuming that fromIterable will consuming from that list in the order that it is received by the spring boot application.
It feels like I am violating some sort of principle here...I mean it works...

Meh sorted it out.
So there is the expectNext method:
https://www.baeldung.com/flux-sequences-reactor
Where you can pregenerate your list and then assert something like this:
List<String> expectedStrings = Arrays.asList(
"hello1", "hello2", "hello3"
);
...
StepVerifier.create(result)
.expectNextSequence(expectedStrings)
.verifyComplete();
EDIT: apparently I have to wait a few days before I can mark my own question as answered?

Related

How to do multiple API calls concurrently in Spring service without changing main?

I need to make a service in an existing fat code to get results from 4 APIs and I need to merge them and reformat each responses, but it takes very slow due to 4 calls that I don't know how to do it concurrently. I am also unable to change the main to add Runnable or such executor in the main as it may have snowballing effect to another code.
So currently, I have made a controller which handle the request, a service which get the request from user and call 5 different service-middleware (SM) functions. Every SM functions used to call an external API, and in every SM, I reformat each return map of the APIs there as well. I use java.net.HttpURLConnection to do the API calls. Thus, I got my API "worked" but can't be faster than 4 seconds. Those APIs needs additional OAuth, so it would be roughly 10 API calls in total.
Since the current returns of API calls are Object type, I can treat it as Map, and reformat the output by doing looping for the data inside it. So the SM function would likely have the code similarly to below:
token = sendHttpRequest(authUrl, authRequestHeader, null, null, "GET");
Map response = sendHttpRequest(url, requestHeader, bodyParam, null, "POST");
List<Map> data = (List) ((Map) response.get("output")).get("data");
List<Map> result = new HashMap();
for(Map m : data) {
Map temp = new HashMap();
temp.put("name", m.get("Name"));
temp.put("health_status", m.get("HealthStatus"));
result.add(temp);
}
// This format is mandatory
Map finalResult = new HashMap();
finalResult.put("output", result);
finalResult.put("status", "OK");
return finalResult;
And the sendHttpRequest is the method to send request, serializing params to JSON and deserializing API output to be an Object. Here's the sendHttpRequest look like:
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();
HttpComponentsClientHttpRequestFactory requestFactory =
new HttpComponentsClientHttpRequestFactory();
requestFactory.setConnectTimeout(this.connectTimeOut);
requestFactory.setReadTimeout(this.readTimeOut);
requestFactory.setHttpClient(httpClient);
RestTemplate rt = new RestTemplate(requestFactory);
HttpEntity<Map> request = null;
if(method.equals("POST"))
request = new HttpEntity<Map>(objBody, headers);
else if(method.equals("GET"))
request = new HttpEntity<Map>(headers);
try {
ResponseEntity<Map> response = null;
if(method.equals("POST"))
restTemplate.postForEntity(url, request , Map.class);
if(method.equals("GET"))
restTemplate.postForEntity(url, request , Map.class);
if(this.outputStream){
logger.debug("Output : " + response.getBody());
}
return response.getBody();
} catch(HttpClientErrorException e) {
logger.debug(e.getMessage());
}
The sendHttpRequest method is also an existing method that I am disallowed to change except if I just make a new method for doing my requests only.
Simply say, here's the things I need to do:
For each of the API calls:
Get the Authorization token from an external API.
Do the request (POST/GET) to another external API to get data.
Reformat the data to be expected format for response (each has its own format) <Mostly loop the array of the response object to remap the field names as it's necessary>.
After all APIs finished calling, I need to do:
Merge output from API 1 and 3 to a Map/Object
Merge output from API 2 & 4 to an Array and sort them all
Put response from API 5 in the inner object of a defined attribute/field.
Things I had tried
I had tried the use of ExecutorCompletionService to call the 5 SMs. I also created an inner class that implements Callable for this.
ExecutorService executor = Executors.newFixedThreadPool(5);
CompletionService completionService = new ExecutorCompletionService<>(executor);
List<Future<Map>> results = new ArrayList<>();
for(int i=1; i<6; i++) {
// i here is used to define which api calls to be done
results.add(completionService.submit(new CallAPIClass(paramMap, i)));
}
for (int i=0; i < results.size(); i++) {
try {
Map result = (Map) completionService.take().get();
int code = (int) result.get("code");
// Collect the results for each SM (SM function has described above)
} catch (Exception e) {
logger.debug(e.getMessage());
}
}
// Merge the outputs.
In the Merge the outputs, I need to construct the map, so it will be like this:
{
"details": {"api1": {...}, "api3": {...}},
"list_items": [{...}, {...}, ...], // Results of sorted merged lists from api2 & api4
"api5": [{...}, {...}, {...}, ...]
}
Meanwhile, from the api responses, basically I just retrieve all of their output_schema when exists.
Any tips to optimize and speed up this API call, so by the same number of calls, this can be executed faster??? Any help is greatly appreciated.
Edit
I have read #Ananthapadmanabhan's answer, but it seems that I need to change the main class file which I can't do. Or is it actually possible to apply the use of CompletableFuture without using #EnableAsync in main class?
I also wonder how to get this done in a faster time even with CompletableFuture and EnableAsync with this chain of processes.
The solution you tried looks quite decent to me:
ExecutorService executor = Executors.newFixedThreadPool(5);
CompletionService completionService = new ExecutorCompletionService<>(executor);
List<Future<Map>> results = new ArrayList<>();
for(int i=1; i<6; i++) {
// i here is used to define which api calls to be done
results.add(completionService.submit(new CallAPIClass(paramMap, i)));
}
for (int i=0; i < results.size(); i++) {
try {
Map result = (Map) completionService.take().get();
int code = (int) result.get("code");
// Collect the results for each SM (SM function has described above)
} catch (Exception e) {
logger.debug(e.getMessage());
}
}
// Merge the outputs.
I am not quite sure if, in addition to a probably more fluent API, using CompletableFuture will give you any benefit related to the performance of the program - the subject has been broadly discussed here in SO, see for example 1 2 3 - but here it is a possible solution.
In fact, the next code is based in one of my previous answers, in turn closely related to this article from the Tomasz Nurkiewicz blog.
The CompletableFuture counterpart for the code you provided will look like:
ExecutorService executor = Executors.newFixedThreadPool(5);
// List of the different parameters to perform every external API invocations
final List<Map> smParameters = Arrays.asList(
...
);
// Submit invoke external task to the thread pool
final List<CompletableFuture<Map>> futures = smParameters.stream().
map(paramMap -> CompletableFuture.supplyAsync(() -> invokeExternalAPI(paramMap), executor)).
collect(Collectors.<CompletableFuture<Map>>toList())
;
// The next code is based on the sequence method proposed in the blog I cited
// The idea is to turn the `List<CompletableFuture<Map>>` we have into a
// CompletableFuture<List<Map>> with the results of every single async task
final CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
final CompletableFuture<List<Map>> allDone = allDoneFuture.thenApply(v ->
futures.stream().
map(future -> future.join()).
collect(Collectors.<Map>toList())
);
// Merge the outputs.
final Map result = allDone.thenAccept(results ->
// Merge the outputs. The results variable contains the different Mapz
// obtained from the every different API invocation
);
Please, verify the above code, probably it will require the definition of the type of the different parameters of your Map objects.
The mentioned invokeExternalAPI could accept a Map with the different parameters required to perform the individual APIs invocations, something like:
private Map invokeExternalAPI(Map configuration) {
// Pass and extract from the configuration the authUrl, etcetera, everything you need to
// Your code...
token = sendHttpRequest(authUrl, authRequestHeader, null, null, "GET");
Map response = sendHttpRequest(url, requestHeader, bodyParam, null, "POST");
List<Map> data = (List) ((Map) response.get("output")).get("data");
List<Map> result = new HashMap();
for(Map m : data) {
Map temp = new HashMap();
temp.put("name", m.get("Name"));
temp.put("health_status", m.get("HealthStatus"));
result.add(temp);
}
// This format is mandatory
Map finalResult = new HashMap();
finalResult.put("output", result);
finalResult.put("status", "OK");
return finalResult;
}
I think you don't need to modify your main class nor any configuration, as the solution is pure Java based.
Please, bear in mind that this generic approach can be customized to accommodate different requirements.
For example, according to your comments, it seems that you need to invoke from your service the functionality implemented in your different services middleware.
In order to define the list of tasks you wanna perform concurrently you could try something like the following instead of my initial suggestion:
List<CompletableFuture<Map>> futures = new ArrayList<>(5);
// Obtain a reference to the second middleware, and submit it
final ServiceMiddleware1 sm1 = new ServiceMiddleware1();
final CompletableFuture<Map> sm1Cf = CompletableFuture.supplyAsync(() -> sm1.doYourStuff(), executor);
futures.add(sm1Cf);
// Now obtain a reference to the second middleware, and submit it again
final ServiceMiddleware2 sm2 = new ServiceMiddleware2();
final CompletableFuture<Map> sm2Cf = CompletableFuture.supplyAsync(() -> sm2.doYourStuff(), executor);
futures.add(sm2Cf);
// the rest of service middleware. I think here a common interface
// or some kind of inheritance could be of help in the invocation
// At the end, you will get the list of futures you wanna execute in parallel
// The rest of the code is the same
final CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
final CompletableFuture<List<Map>> allDone = allDoneFuture.thenApply(v ->
futures.stream().
map(future -> future.join()).
collect(Collectors.<Map>toList())
);
// Merge the outputs.
final Map result = allDone.thenAccept(results ->
// Merge the outputs. The results variable contains the different Mapz
// obtained from the every different API invocation
);
To deal with errors you have several options.
One obvious is to handle the error in the service middleware itself, in such a way that it will never raise any exceptions, but return some kind of information in its result Map like result code, status, etcetera.
CompletableFuture itself gives you different options as well to deal with errors. As you probably need to perform some changes in the result Map you can, when necessary, use the handle method. It basically takes as argument the result and the hypothetical exception obtained in the execution of the task associated with a CompletableFuture, and return a new one CompletableFuture with the appropriate customizations, based on that result and possible error. For example, in your 4th and 5th service middlewares, which seems to raise errors, you can use something like:
final ServiceMiddleware4 sm4 = new ServiceMiddleware4();
final CompletableFuture<Map> sm4Cf = CompletableFuture.supplyAsync(() -> sm4.doYourStuff(), executor)
.handle((result, exception) -> {
if (exception == null) {
return result;
}
Map actualResult = new HashMap();
actualResult.put("errorCode", "xxx")
actualResult.put("errorMessage", exception.getMessage());
return actualResult;
});
)
;
futures.add(sm4Cf);
This great article, for instance, explains in detail further error handling approaches.
All these approaches assume that your code doesn't throw checked exceptions. If you need to deal with them, as it seems according to your comment, you could use a modified version of the code that Holger posted in this SO answer. The idea is to create a method that will handle the check exception, completing it with the appropriate error if necessary:
public static <T> CompletableFuture<T> supplyAsync(Supplier supplier, Executor executor) {
CompletableFuture<T> f=new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
try { f.complete(supplier.get()); } catch(Throwable t) { f.completeExceptionally(t); }
}, executor);
return f;
}
Then, use this method to submit every service middleware task:
List<CompletableFuture<Map>> futures = new ArrayList<>(5);
// Obtain a reference to the second middleware, and submit it
final ServiceMiddleware1 sm1 = new ServiceMiddleware1();
final CompletableFuture<Map> sm1Cf = supplyAsync(() -> sm1.doYourStuff(), executor)
// this method will only be executed if any exception is thrown
.exceptionally(exception -> {
Map errorResult = new HashMap();
errorResult.put("errorCode", "xxx")
errorResult.put("errorMessage", exception.getMessage());
return errorResult;
});
futures.add(sm1Cf);
// Apply a similar logic to the rest of services middlewares...
// The rest of the code is the same as above
final CompletableFuture<Void> allDoneFuture =
CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
final CompletableFuture<List<Map>> allDone = allDoneFuture.thenApply(v ->
futures.stream().
map(future -> future.join()).
collect(Collectors.<Map>toList())
);
// Merge the outputs.
// Please, be aware that in the lambda expression results
// is a List of the different Maps obtained as the individual
// results of every single service middleware call
// I would create an object that agglutinates these results in
// the right format, as you indicated in your question. Let's call
// this container class ServiceMiddlewareResult. Then, the merge
// results code will looks like similar to this
final ServiceMiddlewareResult result = allDone.thenAccept(results -> {
ServiceMiddlewareResult serviceMiddlewareResult = new ServiceMiddlewareResult();
// Variable used for storing temporarily the Api 2 and 4 results
// Parameterize it as necessary
List tempResultsFromApi2AndApi4 = new ArrayList();
// Honestly I don't remember if the order of the results is the
// same as the order of the futures that generated them, my guess
// is that not, as it depends on the actual future completion,
// but in any way I always try thinking that the results can be
// in any order, so it is important that every Map contains the
// minimal information to identify the corresponding service
// middleware. With that assumption in mind, your code will look
// similar to this:
results.forEach(result -> {
// The suggested idea, identify the service middleware that
// produced the results
String serviceMiddleware = result.get("serviceMiddleware");
switch(serviceMiddleware) {
// handle every case appropriately
case 'sm1': {
// it should be similar to sm3
serviceMiddlewareResult.getDetails().setApi1(...);
break;
}
case 'sm2':
case 'sm4': {
// Extract results from the Map, and add to the temporary list
tempResultsFromApi2AndApi4.add(...)
break;
}
case 'sm5': {
// extract results and populate corresponding object
serviceMiddlewareResult.setApi5(...);
break;
}
}
});
List sortedResultsFromApi2AndApi4 = Collections.sort(
sortedResultsFromApi2AndApi4, ... the appropriate comparator...
);
result.setListItems(sortedResultsFromApi2AndApi4);
return result;
});
I modified the example to provide a posible approach to merge your results.
Please, consider include logging information within your service middleware code if you need to trace and improve the debugging capabilities offered by the overall solution.
If you have used them before, as an alternative, you could try solutions based in libraries like RxJava or Project Reactor as well.
If all the 4 api calls are independent of each other and you are using java 8 , you could extract them to separate functions in a separate service layer if needed and use spring #Async annotation on the method along with CompletableFuture as return type to make parallel calls.
#Service
public class TestClient {
RestTemplate restTemplate = new RestTemplate();
#Async
public CompletableFuture<List<TestPojo>> getTestPojoByLanguage(String language) {
String url = "https://test.eu/rest/v2/lang/" + language + "?fields=name";
Country[] response = restTemplate.getForObject(url, Country[].class);
return CompletableFuture.completedFuture(Arrays.asList(response));
}
#Async
public CompletableFuture<List<TestPojo>> getCountriesByRegion(String region) {
String url = "https://testurl.eu/rest/v2/region/" + region + "?fields=name";
Country[] response = restTemplate.getForObject(url, Country[].class);
return CompletableFuture.completedFuture(Arrays.asList(response));
}
}
Completable Future guide.

How to call Synchronus blocking method inside async method in Java?

I'm Using Project Reactor library. Here is my scenario.
I want to call the blocking service inside my non blocking method.
I have a three different services, I called those three services from my springboot application. Here is my sample code
public Mono<Example> getValuesFromDifferentServices() {
Mono<Example1> mono1=service.getService1();
Mono<Example2> mono2=service.getService2();
mono1.zipwith(mono2)
.map(value-> {
// some logics then
if(value.getT1().getStatus().equals(value.getT2().getStatus())) {
Mono<Example3> mono3 = service.getService3(true);
mono3.map(f-> {
value.getT1().setSomething(f.getSomething);
return f;
}).subscribe();
}
return value.getT1();
})
}
Note: Above example is not the actual logic. But the implementation is similar to that
Even I tried to subscribe() it, I couldn't get the 3rd service value all the time (uncertainty values). I cannot block() the 3rd service since it is not allowed. How to achieve this?
Update: 3rd Service input would be decided after If condition either it should be true or not Mono<Example3> mono3 = service.getService3(true);
We should call the 3rd service if only the condition matches, otherwise calling the 3rd service is not required and which is not advisable., If condition doesn't match, we should not invoke 3rd service.
This example is a little wierd but as I understand, you want to call the first two services, each give you back a single value.
After that you want to call the third one if necessary and set a value from this into one of the first's field.
Anyway, there is a simple solution, but with more information maybe we can create nicer stream. This stream takes adventages of flatMap, which eagerly subscribes into the inner publisher.
[The example was written in Kotlin, it's very like Java. The only confusig thing here maybe the it variable, which is equals something like this: map(it -> it.sg )]
data class Example(
val name: String,
val status: String,
var value: String? = null
)
class ReactorTest {
#Test
fun test() {
val first = Mono.just(Example("first", "suspended"))
val second = Mono.just(Example("second", "suspended"))
val third = Mono.just(Example("third", "suspended", "thirdValue"))
val stream = first.zipWith(second)
.flatMap { tuple ->
Mono.just(tuple.t1)
.filter { it.status == tuple.t2.status }
.zipWith(third)
.doOnNext {
it.t1.value = it.t2.value
}
.map { it.t1 }
.switchIfEmpty(Mono.just(tuple.t1))
}
StepVerifier.create(stream)
.expectNext(Example("first", "suspended", "thirdValue"))
.verifyComplete()
}
#Test
fun test2() {
val first = Mono.just(Example("first", "suspended"))
val second = Mono.just(Example("second", "active"))
val third = Mono.just(Example("third", "suspended", "thirdValue"))
val stream = first.zipWith(second)
.flatMap { tuple ->
Mono.just(tuple.t1)
.filter { it.status == tuple.t2.status }
.zipWith(third)
.doOnNext {
it.t1.value = it.t2.value
}
.map { it.t1 }
.switchIfEmpty(Mono.just(tuple.t1))
}
StepVerifier.create(stream)
.expectNext(Example("first", "suspended"))
.verifyComplete()
}
}
Side note: if you're using blocking services in your reactive streams, those should be separated into dedicated threadpools. Like:
fun blockingService(): Mono<String> {
//real service use fromCallable
return Mono.just("fromCallableOnServiceCall")
//for real service it may use a dedicated pool
.subscribeOn(Schedulers.boundedElastic())
}

Chained reactive components invocation

I have a list of following objects with method returning reactive type Mono<?>:
interface GuyWithReactiveReturnTypeMethod {
Mono<String> execute();
}
class ReactiveGuysInvocator {
Mono<String> executeAllGuys(List<GuyWithReactiveReturnTypeMethod> guysToInvoke) {
???
}
}
And I need to invoke all the guys one by one (n's guy result is n+1's guy argument), but I'm not sure how can I iterate over such list.
I thought of flatMaping next guy in a while loop:
public interface GuyWithReactiveReturnTypeMethod {
Mono<String> execute(String string);
}
class ReactiveGuysInvocator {
Mono<String> executeAllGuys(List<GuyWithReactiveReturnTypeMethod> guysToExecute) {
ListIterator<GuyWithReactiveReturnTypeMethod> iterator = guysToExecute.listIterator();
Mono<String> currentResult = Mono.just("start");
while (iterator.hasNext()) {
GuyWithReactiveReturnTypeMethod guyToInvoke = iterator.next();
currentResult = currentResult.flatMap(guyToInvoke::execute)
.doOnNext(object -> System.out.println("Executed!"))
.doOnError(error -> System.out.println("Error"));
}
return currentResult;
}
}
But this approach seems to be completely incorrect.
Does anyone know how could I implement something like this?
UPDATE: flatMap can be easily abused. Make sure that you are doing asynchronous work when using flatMap. Mostly, it seems to me, that you can do pretty well with a minimum of Mono.just.
Flatmap is what you have to do with the constraints you provide.
executeAllGuys(Arrays.asList(new GuyWithReactiveReturnTypeMethod[] {
(s)->Mono.just(s+"1"),
(s)->Mono.just(s+"2"),
(s)->Mono.just(s+"3")}))
.subscribe(System.out::println);
Mono<String> executeAllGuys(List<GuyWithReactiveReturnTypeMethod> guysToExecute) {
// your flow is starting here
Mono<String> stringMono = Mono.just("start");
for ( GuyWithReactiveReturnTypeMethod guyToInvoke: guysToExecute) {
stringMono = stringMono.flatMap(guyToInvoke::execute);
}
return stringMono;
}
Just look at all those Mono.just calls. Why do you want to create N+1 flows to do the job? The real problem is you're creating a new flow every time you execute the interface method. Flatmap stops the current flow and starts a new one with the publisher returned by the flatMap method. Try to think reactive and treat the whole business like a stream. There is no flatMap in Streams. A reactive execution should be done on only a single flow.
A Mono<String> execute(String string) is not a reactive component. It is a reactive producer. A Mono<String> execute(Mono<String> string) is a reactive component.
Make your interface more reactive by taking a Mono in and returning a Mono. Your application is doing a map conversion on at each step. This is "chaining reactive components".
executeAllGuys(Arrays.asList(new GuyWithReactiveReturnTypeMethod[] {
(s)->s.map(str->str+"1"),
(s)->s.map(str->str+"2"),
(s)->s.map(str->str+"3")}))
.subscribe(System.out::println);
Mono executeAllGuys(List guysToExecute) {
// your flow is starting here
Mono stringMono = Mono.just("start");
for ( GuyWithReactiveReturnTypeMethod guyToInvoke: guysToExecute) {
stringMono = guyToInvoke.execute(stringMono);
}
return stringMono;
}
interface GuyWithReactiveReturnTypeMethod {
Mono execute(Mono string);
}
Make your interface less reactive but make your application more reactive by using a Flux instead of a list. You will then have to use reduce to convert a Flux to a Mono. Your application is doing a Map/Reduce function. I don't think a Flux will guarantee execution order of the elements in the flow but it could executeAllGuys more efficiently.
// your flow is starting here
executeAllGuys(Flux.just(
(s)->s+"1",
(s)->s+"2",
(s)->s+"3"))
.subscribe(System.out::println);
Mono executeAllGuys(Flux guysToExecute) {
return guysToExecute.reduce("start", (str, guyToInvoke)->guyToInvoke.execute(str));
}
interface GuyWithReactiveReturnTypeMethod {
String execute(String string);
}
Reference: Reactive Programming: Spring WebFlux: How to build a chain of micro-service calls?

how to solve the problem with put one data in mono to another mono in Spring Webflux?

I want to get a string data from another server by webclient object, and put it to another Mono object. But in a webclient, only readable that in .subscribe().
Because responseBody.subscribe() method is async, method test() will be return result object with empty message field before responseBody.subscribe() executed.
Of course, I knew that if I return responseBody object instead of result object, there is no problem. But I want to return not a responseBody object but result object with not empty field of message.
I want to return result when responseBody's subscribe() is completed.
How to change my code?
Please help me.
public Mono<ResultVO> test() {
Mono<ResultVO> result = Mono.just(new ResultVO());
WebClient client = webClientBuilder.baseUrl("http://XXXXXX").build();
Mono<String> responseBody = client.get().uri("/aaaa/bbbbb").retrieve().bodyToMono(String.class);
responseBody.subscribe( s -> {
result.subscribe(g -> g.setMessage(s));
});
return result;
}
...
#Data
public class ResultVO {
private long timestamp;
private String ip;
private String message;
...
}
I expect like this
{
"timestamp": 1566662695203,
"ip": "192.168.1.1",
"message": "c0db76f6-4eb5-4f84-be8d-018d53b453bb"
}
But result data is,
{
"timestamp": 1566662695203,
"ip": "192.168.1.1",
"message": ""
}
Putting this kind of logic into the subscribe method is not recommended, it can easily lead to 'callback hell' and eventually unmaintainable code. Also, I don't see the caller of the shared test method, but chances are that one of the Monos is subscribed twice, which also leads to quite confusing behaviour.
Instead, to combine Monos you can use zip, zipWith, flatMap and a couple of other operators.
One solution with zipWith method:
public Mono<ResultVO> test()
{
WebClient client = WebClient.builder().baseUrl("http://XXXXXX").build();
// dummy representation of another data source (db query, web service call...)
Mono<ResultVO> result = Mono.just(new ResultVO());
Mono<String> responseBody = client.get().uri("/aaaa/bbbbb").retrieve().bodyToMono(String.class);
return result.zipWith(responseBody,
(resultObj, body) -> new ResultVO(resultObj.getTimestamp(), resultObj.getIp(), body));
}
Couple of other notes:
If you are returning JSON through a REST endpoint of your reactive WebFlux application, then you never need to subscribe manually, Spring will do that for you
Avoid using mutable objects (the ones which you modify after creation with setters), instead create new object, this will make your code easier to reason about and less prone to concurrency issues
Useful read about available Reactor operators
First of all, you hardly ever subscribe in your own application.
Think of it this way. Your server is a publisher, that means that your server fetches data and then publishes it to whomever wants it.
The subscriber is usually the end client, that could be a react application, an angular application or any client.
I think you need to read up on the basics of how to use webflux and reactive programming.
This is how to do what you are asking for, with as minimal changes to your code, we map what we fetched to what we want returned.
public Mono<ResultVO> test() {
final WebClient client = webClientBuilder
.baseUrl("http://XXXXXX").build();
return client.get()
.uri("/aaaa/bbbbb")
.retrieve()
.bodyToMono(String.class)
.map(message -> {
final ResultVO resultVO = new ResultVO();
resultVO.setMessage(message);
return resultVO;
}
);
}

JUnit testing tips Java

I need to test the following code using JUnit. It looks complex to me and I am not even sure where to take a start from.
I know what the method is doing but I am unable to write a JUnit test for it. Do we follow a pattern or keep some key points in mind while testing any piece of code.
protected WebResource.Builder applyHeaders(WebResource service, List<? extends BaseClientHeader<?>> headers, List<HttpRequestClientHeader> httpHeaders) {
WebResource.Builder wrb = service.getRequestBuilder();
if( headers != null ) {
for( BaseClientHeader<?> header : headers ) {
wrb = wrb.header( ((IEnum)header.getName()).value(), header.getValue() );
}
}
if( httpHeaders != null ) {
for( HttpRequestClientHeader header : httpHeaders ) {
wrb = wrb.header( header.getName().value(), header.getValue() );
}
}
return wrb;
}
Thanks,
Even when this method looks like it does many different things and interacts lots of other code it should be rather simple to test; that’s because it only operates on objects that you hand in. Let’s see…
#Test
public void requestBuilderIsReturned() {
WebResource webResource = Mockito.mock(WebResource.class);
WebResource.Builder webResourceBuilder = mock(WebResource.Builder.class);
when(webResource.getRequestBuilder()).thenReturn(webResourceBuilder);
WebResource.Builder createdBuilder = objectUnderTest.applyHeaders(webResource, null, null);
assertThat(createdBuilder, is(webResourceBuilder));
}
That was pretty straight-forward. In order to verify correct operation on both kinds of headers you need to get a bit tricky, I suppose:
when(webResourceBuilder.header(anyString(), anyString())).thenReturn(webResourceBuilder);
This will simply make the header() method return the object it’s called upon. After that it should be quite simple to verify that the correct methods were called:
verify(webResourceBuilder).header("header1", "value1");
verify(webResourceBuilder).header("header2", "value2");
Armed with this you should be able to unit test the shit out of this particular method. :)

Categories

Resources