Spring rest docs using webTestClient with StepVerifier (Reactive endpoints) - spring

I am trying to create unit tests for my controllers using webTestClient. I need to generate api documentation using Spring rest docs. I used to create my assets using StepVerifier (Endpoints returning Mono of Flux)
Is there anyway of using StepVerifier and create automatic documentation using Spring rest docs.
This code works fine:
val result = webTestClient.get()
.uri("/api/clients")
.exchange()
.expectStatus().isOk
.expectBody().consumeWith(document("client-getAll"))
Which would be the way to define test using stepverifier?
val result = webTestClient.get()
.uri("/api/clients")
.exchange()
.expectStatus().isOk
.returnResult<Client>().responseBody
StepVerifier.create(result)
.expectNextCount(1)
.verifyComplete()

Related

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

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.

Spring Security 5 rest client with OAuth2

I would like to implement a client which should simply send some rest calls with a OAuth2 token. Using spring-security-oauth it was pretty easy to use the OAuth2RestTemplate with a client-credentials flow. Today I saw most of those classes are deprecated in 2.4.0 and the recommendation is to use Spring Security 5. I've googled around and looked into the Migration Guide [1] but I've not understood what I've to do to perform some simple rest call which fetches a token with Spring Security 5. I think I'm even not sure what kind of libraries are needed. So what I'm basically is looking for is a way to provide a client-id, client-secret and a tokenendpoint programatically (not via properties) to some kind of rest template and send a request to a specific url.
--edit--
I found a way of using the web client without using the properties but rather using the ClientRegestration object. I'm not sure if that is a recommended way:
#Test
public void test() {
WebClient webClient = getWebClient();
ResponseSpec retrieve = webClient.get().uri("https://somepath")
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId(REG_ID)).retrieve();
Flux<String> result = retrieve.bodyToFlux(String.class); // flux makes no sense here, use Mono instead
Mono<List<String>> response = result.collectList();
List<String> block = response.block();
System.out.print(block);
System.out.print("debug");
}
public WebClient getWebClient() {
Builder clientRegestrationBuilder = ClientRegistration.withRegistrationId(REG_ID);
clientRegestrationBuilder.clientId(CLIENT_ID);
clientRegestrationBuilder.clientSecret(CLIENT_SECRET);
clientRegestrationBuilder.tokenUri(TOKEN_ENDPOINT);
clientRegestrationBuilder.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);
ClientRegistration clientRegistration = clientRegestrationBuilder.build();
ReactiveClientRegistrationRepository repo = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(repo,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
return WebClient.builder().filter(oauth).build();
}
Regards
monti
[1] https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide
The following code is a unit test which shows how a ClientRegistration can be done programatically. In "real" spring scenario I guess the ClientRegistration should be provided as bean and finally injected as a list to a ReactiveClientRegistrationRepository...
public void test() {
WebClient webClient = getWebClient();
ResponseSpec retrieve = webClient.get().uri("https://somepath")
.attributes(ServerOAuth2AuthorizedClientExchangeFilterFunction.clientRegistrationId(REG_ID)).retrieve();
Flux<String> result = retrieve.bodyToFlux(String.class); // flux makes no sense here, use Mono instead
Mono<List<String>> response = result.collectList();
List<String> block = response.block();
System.out.print(block);
System.out.print("debug");
}
public WebClient getWebClient() {
Builder clientRegestrationBuilder = ClientRegistration.withRegistrationId(REG_ID);
clientRegestrationBuilder.clientId(CLIENT_ID);
clientRegestrationBuilder.clientSecret(CLIENT_SECRET);
clientRegestrationBuilder.tokenUri(TOKEN_ENDPOINT);
clientRegestrationBuilder.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);
ClientRegistration clientRegistration = clientRegestrationBuilder.build();
ReactiveClientRegistrationRepository repo = new InMemoryReactiveClientRegistrationRepository(clientRegistration);
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(repo,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
return WebClient.builder().filter(oauth).build();
}

How to apply Spring Cloud Gateway GlobalFilter on WebClient in a RouterFuncation bean?

Currently I am trying to use Spring Cloud Gateway(Spring Cloud version: Finchley.M5) in a edge-service, my sample has a Spring Session header(X-AUTH-TOKEN) token based authentication.
In Gateway specific RouteLocator, the authentication works well, because the built-in GlobalFilters are applied on the RouteLocator when it passes request to downstream microservice.
But I want to create a generic RouterFunction to consume some resources of downstream services and aggregate them together in a new edgeservice, the GlobalFileters will not apply on my webclient bean.
#Bean
RouterFunction<ServerResponse> routes(WebClient webClient) {
log.debug("authServiceUrl:{}", this.authServiceUrl);
log.debug("postServiceUrl:{}", this.postServiceUrl);
log.debug("favoriteServiceUrl:{}", this.favoriteServiceUrl);
return route(
GET("/posts/{slug}/favorited"),
(req) -> {
Flux<Map> favorites = webClient
.get()
.uri(favoriteServiceUrl + "/posts/{slug}/favorited", req.pathVariable("slug"))
.retrieve()
.bodyToFlux(Map.class);
Publisher<Map> cb = from(favorites)
.commandName("posts-favorites")
.fallback(Flux.just(Collections.singletonMap("favorited", false)))
.eager()
.build();
return ok().body(favorites, Map.class);
}
)
...
Is there a simple solution to apply Gateway Filter also work on the RouterFunction, thus my header token based authentication can work automatically?
Or there is a simple way to pass current X-AUTH-TOKEN header into the downstream webclient request headers?
In traditional MVC, there is a RequestContext used to get all headers from current request context and pass them to the downstream request in a global filter. Is there an alternative of RequestContext in webflux to read all headers from current request context?
The complete codes is here.

How do I use okhttp in the spring cloud ribbon

The getting started of the spring cloud ribbon is very easy and simple, and it is using the rest template to communicate with backend servers.
But in our project we are more like to use okhttp to do the http request, does anyone can help?
You can take a look at the spring-cloud-square project which supplies integration with Square's OkHttpClient and Netflix Ribbon via Spring Cloud Netflix, on the Github. Let's see a test method in the OkHttpRibbonInterceptorTests.java class
#Test
#SneakyThrows
public void httpClientWorks() {
Request request = new Request.Builder()
// here you use a service id, or virtual hostname
// rather than an actual host:port, ribbon will
// resolve it
.url("http://" + SERVICE_ID + "/hello")
.build();
Response response = builder.build().newCall(request).execute();
Hello hello = new ObjectMapper().readValue(response.body().byteStream(), Hello.class);
assertThat("response was wrong", hello.getValue(), is(equalTo("hello okhttp")));
}

Mocktesting RestTemplate using Mockito in Spring boot latest version

I am using 1.3.3.RELEASE version of Spring Boot.
I am trying to unit test my service which uses RestTemplate to call a url for fetching html page.
This is the code
Mockito.when(restTemplate.exchange(
Mockito.anyString(),
Mockito.any(HttpMethod.class),
Mockito.any(HttpEntity.class),
Mockito.any(Class.class)))
.thenReturn(new ResponseEntity<String>("",
new HttpHeaders(), HttpStatus.OK));
But thenReturn part is having issues. What could be the right construction of ResponseEntity for getting an html page.
Appreciate any help.
Thanks
you need to mock out responseEntity
so:
ResponseEntity<String> mockResponse = mock(ResponseEntity.class);
when(mockResponse.getStatusCode()).thenReturn(HttpStatus.OK);
then like you did before:
when(mockRestTemplate.exchange(anyString(), anyObject(), anyObject(), eq(String.class)))
.thenReturn(mockResponse);

Resources