Mockito - How can I test the behaviour based on HttpStatusCode? - spring

I have this request with WebClient:
webClient
.get()
.uri(uri)
.accept(MediaType.APPLICATION_OCTET_STREAM)
.<Optional<ByteArrayResource>>exchangeToMono(response -> {
if (response.statusCode().equals(HttpStatus.NOT_FOUND)) {
return Mono.just(Optional.empty());
}
return response.bodyToMono(ByteArrayResource.class).map(Optional::of);
})
.block();
How can I test the logic inside exchangeToMono()?
I'm using Mockito for testing this way:
given(headersSpecHeadOpMock.exchangeToMono()).willReturn(Mono.just(clientResponse))
But the problem here is that this way I'm not testing the HttpStatus.NOT_FOUND.

The problem I was having is that I was mocking the ClientResponse, but I would have to Mock a Function<ClientResponse, ? extends Mono<Optional<ByteArrayResource>>>.
The solution was to use an ArgumentCaptor to get the argument and then make the assert of his value like this:
ArgumentCaptor declaration:
ArgumentCaptor<Function<ClientResponse, ? extends Mono<Optional<ByteArrayResource>>>> captorLambda = ArgumentCaptor.forClass(Function.class);
Capture the argument:
given(headersSpecGetOpMock.<Optional<ByteArrayResource>>exchangeToMono(captorLambda.capture())).willReturn(Mono.just(Optional.empty()));
Assert the value returned by the client response:
assertThat(captorLambda.getValue().apply(clientResponse).block()).isEqualTo(Optional.empty());```

Consider rewriting your code as follows:
webClient
.get()
.uri(uri)
.accept(MediaType.APPLICATION_OCTET_STREAM)
.retrive()
.onStatus(status -> HttpStatus.NOT_FOUND == status, response -> Mono.just(Optional.empty()))
.bodyToMono(ByteArrayResource.class);
.map(Optional::of)
.block();
Now you can mock retrieve() method to test your conditions easily.
As a side note, please consider dropping block() call, this defeats the purpose of using reactive programming.

You can use okhttp to mock the server you are hitting as shown here: https://www.baeldung.com/spring-mocking-webclient
The only issue is that you have to use the .mutate() method on the webclient you are trying to test to edit the basurl to match that of the mock webserver. In my own implementation, I used getters and setters to alter the webclient baseurl and then set it back to the original at the end of the test.

Related

Spring WebClient toFuture() vs. block() - What's the main difference?

My Spring Boot application uses WebClient to make calls to a remote API. I do have some difficulty understanding the difference between the following modes on how to use the WebClient.
Option 1 - using block()
// WebClient
public Boolean updateUser(long id) {
return webClient.post()
.uri(uriBuilder -> uriBuilder.path(USER_PATH).build(id))
.body(Mono.just(payload), User.class)
.exchangeToMono(clientResponse -> Mono.just(clientResponse.statusCode().is2xxSuccessful()))
.block();
}
// Caller
Boolean result = updateUser(5);
Option 2 - using toFuture():
// WebClient
public CompletableFuture<Boolean> updateUser(long id) {
return webClient.post()
.uri(uriBuilder -> uriBuilder.path(USER_PATH).build(id))
.body(Mono.just(payload), User.class)
.exchangeToMono(clientResponse -> Mono.just(clientResponse.statusCode().is2xxSuccessful()))
.toFuture();
}
// Caller
CompletableFuture<Boolean> future = updateUser(5);
Boolean result = future.get();
As far as I understand, using .block() blocks the thread when the WebClient makes its request and waits for a response.
When using toFuture() instead, then the WebClient runs on a different thread, thus it does not block. But is the thread not blocked anyways using the .get() method on the CompletableFuture?
When would I choose one over the other?
In the second option, you allow the caller to decide when to wait, this looks more flexible than the first option.
TL;DR
Mono.toFuture() is not blocking but Mono.toFuture().get() is blocking. block() is technically the same as toFuture().get() and both are blocking.
Mono.toFuture() just transforms Mono into a CompletableFuture by subscribing to it and resolving immediately. But it doesn't mean that you can access result of the corresponding Mono after this. CompletableFuture is still async and you can use methods like thenApply(), thenCompose(), thenCombine(), ... to continue async processing. toFuture().get() is a blocking operation.
CompletableFuture<Double> result = getUserDetail(userId)
.toFuture()
.thenCompose(user -> getCreditRating(user));
where getUserDetail is defined as
Mono<User> getUserDetail(String userId);
Mono.toFuture is useful when you need to combine different async APIs. For example, AWS Java v2 API is async but based on CompletableFuture but we can combine APIs using Mono.toFuture or Mono.fromFuture.

Spring Framework WebFlux Reactive Programming

I am trying to send an object to the endpoint but I do not understand why I can't do it with .get(), why .post() has to be used? What if the endpoint method takes an object and does something with it and returns an object? I may want to send an object to the endpoint which takes the object as an argument. Is there a way to do it? How to pass a customer object to getCustomer() endpoint.
WebClient.create("http://localhost:8080")
.get()//why this can not be used? why post has to be used?
.uri("client/getCustomer")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(customer)//with .get() body cannot be passed.
.retrieve()
.bodyToMono(Customer.class);
#GET
#Path("/getCustomer")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Customer getCustomer(Customer customer) {
//do something
return customer;
}
Edited
In GET methods, the data is sent in the URL. just like:
http://www.test.com/users/1
In POST methods, The data is stored in the request body of the
HTTP request.
Therefore we should not expect .get() method to have .bodyValue().
Now if you wanna send data using GET method, you should send them in the URL, like below snippet
WebClient.create("http://localhost:8080")
.get()
.uri("client/getCustomer/{customerName}" , "testName")
.retrieve()
.bodyToMono(Customer.class);
Useful Spring webClient sample:
spring 5 WebClient and WebTestClient Tutorial with Examples
Further information about POST and GET
HTTP Request Methods

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);
}

How do I get the HTTP status code of a given URL via Spring?

I am working in a Spring #Component class and I am trying to get the HTTP status code of a particular URL for further processing. I have a function as follows:
fun getStatus() : String
{
val webClient = WebClient.create("https://stackoverflow.com")
val result = webClient.get()
.exchange().map { res -> res.rawStatusCode() }
println(result)
return "statusGotten"
}
However, rather than getting the Int value of the status code (e.g. 200, or 401), I am simply getting: "MonoMap".
I am new to both Spring and Web Programming in general, so I'm a little confused how to proceed from here. I'm aware that "result" is being returned as a "Mono", but I'm less clear about what a "Mono" is, or how I might transform it into something with more scrutable properties, as even looking at "result" in the debugger doesn't shed any light as to whether the HTTP request was actually sent or was successful:
Am I calling the webclient incorrectly? Or merely failing to parse the resultant data in a meaningful way? Any suggestions on how or where I might learn more about the underlying topics would be much appreciated as well.
If you need a blocking way to do this is easy just
#Test
public void myTest(){
WebClient client = WebClient.builder().baseUrl("https://stackoverflow.com/").build();
ClientResponse resp = client
.get()
.uri("questions/")
.exchange()
.block();
System.out.println("Status code response is: "+resp.statusCode());
}
But for this you can use directly the RestTemplate instead the webclient... the recomended way to do this is non blocking what means you should return a Mono with the status and consume outside your method like for example:
public Mono<HttpStatus> myMethod(){
WebClient client = WebClient.builder().baseUrl("https://stackoverflow.com/").build();
return client
.get()
.uri("questions/")
.exchange()
.map( clientResp -> clientResp.statusCode());
}
The way of consume this Mono depends of your code...

Missing Content-Length header sending POST request with WebClient (SpringBoot 2.0.2.RELEASE)

I'm using WebClient (SpringBoot 2.0.2.RELEASE) to send a POST with SOAP request, but it is missing "Content-Length" header required by the legacy API.
Is it possible to configure WebClient to include "Content-Length" header?
There is an Spring Framework Issue resolved and introduced for EncoderHttpMessageWriter in SpringBoot 2.0.1, but it seems not to work for JAXB.
I tried to use BodyInserters:
webClient.post().body(BodyInserters.fromObject(request)).exchange();
and syncBody:
webClient.post().syncBody(request).exchange();
None of them worked for WebClient. Though, when RestTemplate is used, Content-Length is set and API responds with success
I am struggling with the same problem, as an ugly work-around I am manually serializing the request (JSON in my case) and setting the length (Kotlin code):
open class PostRetrieverWith411ErrorFix(
private val objectMapper: ObjectMapper
) {
protected fun <T : Any> post(webClient: WebClient, body: Any, responseClass: Class<T>): Mono<T> {
val bodyJson = objectMapper.writeValueAsString(body)
return webClient.post()
.contentType(MediaType.APPLICATION_JSON_UTF8)
.contentLength(bodyJson.toByteArray(Charset.forName("UTF-8")).size.toLong())
.syncBody(bodyJson)
.retrieve()
.bodyToMono(responseClass)
}
}
If you apply Sven's colleague(Max) solution like we did you can also adapt it for cases like your body being a custom obj but you have to serialize it once:
String req = objectMapper.writeValueAsString(requestObject)
and passed that to
webClient.syncBody(req)
Keep in mind that with SpringBoot 2.0.3.RELEASE, if you'll pass a String to webClient as a request, it will put as ContentType header MediaType.TEXT_PLAIN and that made our integration with other service to fail. We fixed that by setting specifically content type header like this:
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
WebClient is a streaming client and it's kind of difficult to set the content length until the stream has finished. By then the headers are long gone. If you work with legacy, you can re-use your mono (Mono/Flux can be reused, Java streams not) and check the length.
public void post() {
Mono<String> mono = Mono.just("HELLO WORLDZ");
final String response = WebClient.create("http://httpbin.org")
.post()
.uri("/post")
.header(HttpHeaders.CONTENT_LENGTH,
mono.map(s -> String.valueOf(s.getBytes(StandardCharsets.UTF_8).length)).block())
.body(BodyInserters.fromPublisher(mono, String.class))
.retrieve()
.bodyToMono(String.class)
.block();
System.out.println(response);
}
A colleague (well done Max!) of mine came up with cleaner solution, I added some wrapping code so it can be tested:
Mono<String> my = Mono.just("HELLO WORLDZZ")
.flatMap(body -> WebClient.create("http://httpbin.org")
.post()
.uri("/post")
.header(HttpHeaders.CONTENT_LENGTH,
String.valueOf(body.getBytes(StandardCharsets.UTF_8).length))
.syncBody(body)
.retrieve()
.bodyToMono(String.class));
System.out.println(my.block());

Resources