How can I use wiremock to test webclients? - spring

What the below code does is that it gets items for specific(using client1) customer and delete them by making another endpoint, client2. The first endpoint provides pagination.
Now, I am having issue with testing it. As you can see, I am using wiremock but it does not seem working properly as the last endpoints never called. I have provided the response body for the first stub, was expecting the next endpoints to be triggered. I tried to debugging by putting break point to see if they are called, but the code never reach the section where the two endpoint are triggered. I am not sure the way I did is correct. Is this the correct approach to test using wiremock?
public void deleteItems(String id) {
client1.get()
.uri(urlBuilder -> urlBuilder
.path(CLIENT_ONE_ENDPOINT)
.queryParam("email", id)
.build())
.retrieve()
.bodyToMono(Items.class)
.expand(items -> {
while (!items.isOnLastPage()) {
int nextPage = items.getCurrentPage() + 1;
return client1.get()
.uri(builder -> builder
.path(CLIENT_ONE_ENDPOINT)
.queryParam("email", id)
.queryParam("pageNumber", nextPage)
.build())
.retrieve()
.bodyToMono(Items.class);
}
return Flux.empty();
})
.flatMapIterable(Items::getResults)
.map(or -> {
return client2.delete()
.uri(CLIENT_TWO_ENDPOINT + or.getId())
.retrieve()
.bodyToMono(ResponseEntity.class)
.subscribe();
})
.subscribe();
}
wiremock test
#Test
void itemsAreRemoved() {
String email = "example#example.com";
String itemId = "001";
WireMock.configureFor(PORT);
wireMockServer.stubFor(get(urlPathEqualTo(CLIENT_ONE_ENDPOINT))
.withQueryParam("email", equalTo(email))
.withHeader("Content-Type", equalTo("application/json"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withBody(loadResponse(itemId))
.withStatus(200)));
wireMockServer.stubFor(get(urlPathEqualTo(CLIENT_ONE_ENDPOINT))
.withQueryParam("email", equalTo(email))
.withQueryParam("pageNumber", equalTo("1"))
.withHeader("Content-Type", equalTo("application/json"))
.willReturn(aResponse()
.withHeader("Content-Type", "application/json")
.withStatus(200)));
wireMockServer.stubFor(delete(urlPathEqualTo(CLIENT_TWO_ENDPOINT + itemId)));
service.deleteItems(email);
verify(getRequestedFor(urlPathEqualTo(CLIENT_ONE_ENDPOINT))
.withQueryParam("email", equalTo(email)));
verify(getRequestedFor(urlPathEqualTo(CLIENT_ONE_ENDPOINT))
.withQueryParam("email", equalTo(email))
.withQueryParam("pageNumber", equalTo("1")));
verify(exactly(1), deleteRequestedFor(urlPathEqualTo(CLIENT_TWO_ENDPOINT + itemId)));
}

Related

Flux.create() not generating events

I'm trying to use Flux to generate asynchronous server sent events using Flux.create. When my client connects the request eventually times out with no event ever received. I hard-coded in an event to be sent by the Flux.create just to see data flow, but still nothing received client side.
#GetMapping(path = "/stream", headers = "Accept=*/*", consumes = MediaType.ALL_VALUE, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<ServerSentEvent<PricingDTO>> getEventStream() {
final Flux<ServerSentEvent<PricingDTO>> flux = Flux.<ServerSentEvent<PricingDTO>>create(emitter -> {
final PricingDTO pricing = new PricingDTO();
pricing.setId(99L);
emitter.next(ServerSentEvent.builder(pricing).build());
});
return flux;
}
Client side (Angular) code:
const eventSource = new EventSource(url);
eventSource.onmessage = (event) => {
console.debug('Received event: ' + event);
const json = JSON.parse(event.data);
// Should be PricingDTO record here
};
eventSource.onerror = (error) => {
if (eventSource.readyState === EventSource.CLOSED) {
console.log('The stream has been closed by the server.');
eventSource.close();
} else {
console.log('Error here: ' + error);
}
};
I never see an event come through the EventSource. Eventually the request times out and I see the error: net::ERR_EMPTY_RESPONSE
I'm new to using WebFlux and I suspect I'm missing some initialization on the FluxStream before I return the Flux result. I have debugged and do see the request being received by my web service and the Flux object being returned. Any idea why I'm not receiving my events?
Your webflux code seems fine. I tested this with the following simplified example (without your custom classes).
#SpringBootApplication
#RestController
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#GetMapping(path = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> getEventStream() {
return Flux.create(emitter -> emitter.next("hi").next("hi2"));
}
}
When connecting to the steam in chrome you get to see the events coming in just fine:
data:hi
data:hi2
the problem either lies in your accept header filter, or on the client side. You could ofcourse validate this by connecting to your stream in a browser (or better, a test)

How can I flatMapMany with multiple web calls and conditionally signal complete?

I need to write a method which does
Gets a Location header form an endpoint
Generates a series of Some which each should retrieved from the Location fetched from the first endpoint.
Need to return a Flux<Some>
Which looks like this.
private WebClient client;
Flux<Some> getSome() {
// Get the Location header from an endpoint
client
.post()
.exchange()
.map(r -> r.headers().header("Location"))
// Now I need to keep invoking the Location endpoint for Some.class
// And Some.class has an attribute for pending/completion status.
// e.g.
while (true)
final Some some = client
.get()
.url(...) // the Location header value above
.retrieve()
.bodyToMono(Some.class)
.block()
;
}
}
How can I map the Mono<String> to a Flux<Some> while using the Some#status attribute for completion?
// should return Flux<Some>
client
.post()
.exchange()
.map(r -> r.headers().header("Location"))
.flatMapMany(location -> {
// invokes location while retrieved Some#status is not `completed`
// ##?
})
;
inside the flatMapMany, perform the call that retrieves the Location. apply a repeat().takeUntil(loc -> loc.complete()) to it (still inside the flatMapMany lambda)

Submitting A Fire-N-Forget Request

In a Spring Boot application, I have the following working code for submitting a request-response request in a Rest controller:
#GetMapping("/request-response")
public ResponseEntity<Mono<Message>> requestResponseCtr() throws InterruptedException {
Mono<Message> message = this.rsocketRequesterMono.flatMap(
requester -> requester.route("request-response")
.data(new Message(...))
.retrieveMono(Message.class)
);
log.info("\nResponse was: {}", message);
return ResponseEntity.of(Optional.of(message));
}
Based on my understanding of the RSocket Java document, I need to replace the retriveveMono(...) with send() for submitting a fire-n-forget request (on page 12 of the document). The following code should work.
#GetMapping("fire-and-forget")
public ResponseEntity<Mono<Void>> fireAndForget() throws InterruptedException {
log.info("\nFire-And-Forget...");
this.rsocketRequesterMono.flatMap(
requester -> requester.route("fire-and-forget")
.data(new Message(...))
.send()
);
return ResponseEntity.of(Optional.of(Mono.empty()));
}
It doesn't work, however. On a TCP debuting tool, there isn't any traffic for it.
What is the right way of submitting a fire-n-forget?
This call
this.rsocketRequesterMono.flatMap(
requester -> requester.route("fire-and-forget")
.data(new Message(...))
.send()
);
isn't doing anything because nothing is subscribing to it.
You likely need
ResponseEntity.of(Optional.of(this.rsocketRequesterMono.flatMap(
requester -> requester.route("fire-and-forget")
.data(new Message(...))
.send()
)));

How to convert the body of a client response to a Mono of a certain type?

So, I send a request using the WebClient and after retrieving the response using exchange() I need to extract the body to a Mono of Object_1. The function needs to return Mono>. Normally you would use bodyToMono(SomeObject.class) but this doesn't work in this case.
public Mono<Object1<Object2>> getAll(String someParam) {
return WebClient.create(this.baseUrl)
.get()
.uri(uriBuilder -> uriBuilder.path("/some_path")
.queryParam("someParam", someParam)
.build())
.exchange()
.flatMap(clientResponse -> clientResponse.bodyToMono(????));
}
I am trying to figure out what to put inside the bodyToMono()
I believe you can do that with the overload of bodyToMono that takes a ParameterizedTypeReference.
ParameterizedTypeReference<Object1<Object2>> typeRef =
new ParameterizedTypeReference<Object1<Object2>>() {};
// . . .
.flatMap(clientResponse -> clientResponse.bodyToMono(typeRef));
You could write it inline, if you prefer and don't find it too hard to read:
.flatMap(clientResponse -> clientResponse.bodyToMono(
new ParameterizedTypeReference<Object1<Object2>>() {}));

Flux<List<?> or Flux<?> in spring webflux

Here is the scenario
a)Tablet use http GET to get users info from another system
b)the other system can add/delete/update users info also and the operations result should show in tablet in time.
To resolve that I use reactor+sse as demo code shows below
In controller
#GetMapping
public Flux<List<User>> listUser() {
return service.listUser();
}
In service
public Flux<List<User>> listUser() {
Flux<List<User>> flux = Flux.generate(
() -> 0,
(state, sink) -> {
synchronized(users) {
users.wait();//other thread will notify with all users info is send to service
}
sink.next(users);
newData = false;
return state + 1;
});
return flux;
}
My questions are,
a)should I use Flux<User> to instead of Flux<List<User>>? I didn't see any usage of Flux<List<T>> so far. also one advantage is that with Flux<User>, I don't need to push all users info to the tablet, only the add/delete/update ones.
b)if I use Flux<List<User>, what should I write for bodyToFlux's parameters in below code
webClient.get()
.uri("/user")
.accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux() //???

Resources