Spring 5 — WebClient and WebTestClient Tutorial | Code Factory
Donate : Link
WordPress Blog : Link
WebClient is part of Spring 5’s reactive web framework called Spring WebFlux. To use WebClient, you need to include the spring-webflux
module in your project.
Add Dependency in an existing Spring Boot project
If you have an existing Spring Boot project, you can add the spring-webflux
module by adding the following dependency in the pom.xml
file -
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Note that, you need Spring Boot version 2.x.x for using the Spring WebFlux module.
Create a new project from Scratch
- Go to http://start.spring.io.
- Set Group and Artifact details.
- Add Reactive Web dependency in the dependencies section.
- Click Generate to generate and download the project.
Consuming Remote APIs using WebClient
We’ll consume Github’s APIs using WebClient. We’ll use WebClient to perform CRUD operations on user’s Github Repositories.
Creating an instance of WebClient
1. Creating WebClient using the create()
method
You can create an instance of WebClient using the create()
factory method -
WebClient webClient = WebClient.create();
If you’re consuming APIs from a specific service only, then you can initialize WebClient with the baseUrl of that service like so -
WebClient webClient = WebClient.create("https://api.github.com");
2. Creating WebClient using the WebClient builder
WebClient also comes with a builder that gives you a bunch of customization options including filters, default headers, cookies, client-connectors etc -
WebClient webClient = WebClient.builder()
.baseUrl("https://api.github.com")
.defaultHeader(HttpHeaders.CONTENT_TYPE, "application/vnd.github.v3+json")
.defaultHeader(HttpHeaders.USER_AGENT, "Spring 5 WebClient")
.build();
Making a Request using WebClient and retrieving the response
Here is how you can use WebClient to make a GET
request to Github’s List Repositories API -
public Flux<GithubRepo> listGithubRepositories(String username, String token) {
return webClient.get()
.uri("/user/repos")
.header("Authorization", "Basic " + Base64Utils
.encodeToString((username + ":" + token).getBytes(UTF_8)))
.retrieve()
.bodyToFlux(GithubRepo.class);
}
We have a class named GithubRepo
(See below for class file) that confirms to the Github’s API response, the above function will return a Flux
of GithubRepo
objects.
Note that, I’m using Github’s Basic Authentication mechanism for calling the APIs. It requires your github username and a personal access token that you can generate from https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line#creating-a-token OR https://github.com/settings/tokens.
Using the exchange() method to retrieve the response
The retrieve()
method is the simplest way to get the response body. However, If you want to have more control over the response, then you can use the exchange()
method which has access to the entire ClientResponse
including all the headers and the body -
public Flux<GithubRepo> listGithubRepositories(String username, String token) {
return webClient.get()
.uri("/user/repos")
.header("Authorization", "Basic " + Base64Utils
.encodeToString((username + ":" + token).getBytes(UTF_8)))
.exchange()
.flatMapMany(clientResponse -> clientResponse.bodyToFlux(GithubRepo.class));
}
Using parameters in the request URI
You can use parameters in the request URI and pass their values separately in the uri()
function. All the parameters are surrounded by curly braces. The parameters will automatically be replaced by WebClient before making the request -
public Flux<GithubRepo> listGithubRepositories(String username, String token) {
return webClient.get()
.uri("/user/repos?sort={sortField}&direction={sortDirection}",
"updated", "desc")
.header("Authorization", "Basic " + Base64Utils
.encodeToString((username + ":" + token).getBytes(UTF_8)))
.retrieve()
.bodyToFlux(GithubRepo.class);
}
Using the URIBuilder to construct the request URI
You can also gain full programmatic control over the request URI using a UriBuilder
like so -
public Flux<GithubRepo> listGithubRepositories(String username, String token) {
return webClient.get()
.uri(uriBuilder -> uriBuilder.path("/user/repos")
.queryParam("sort", "updated")
.queryParam("direction", "desc")
.build())
.header("Authorization", "Basic " + Base64Utils
.encodeToString((username + ":" + token).getBytes(UTF_8)))
.retrieve()
.bodyToFlux(GithubRepo.class);
}
Passing Request Body in WebClient requests
If you have the request body in the form of a Mono
or a Flux
, then you can directly pass it to the body()
method in WebClient, otherwise you can just create a Mono/Flux from an object and pass it like so -
public Mono<GithubRepo> createGithubRepository(String username, String token,
RepoRequest createRepoRequest) {
return webClient.post()
.uri("/user/repos")
.body(Mono.just(createRepoRequest), RepoRequest.class)
.header("Authorization", "Basic " + Base64Utils
.encodeToString((username + ":" + token).getBytes(UTF_8)))
.retrieve()
.bodyToMono(GithubRepo.class);
}
If you have an actual value instead of a Publisher
(Flux
/Mono
), you can use the syncBody()
shortcut method to pass the request body -
public Mono<GithubRepo> createGithubRepository(String username, String token,
RepoRequest createRepoRequest) {
return webClient.post()
.uri("/user/repos")
.syncBody(createRepoRequest)
.header("Authorization", "Basic " + Base64Utils
.encodeToString((username + ":" + token).getBytes(UTF_8)))
.retrieve()
.bodyToMono(GithubRepo.class);
}
Finally, you can use various factory methods provided by BodyInserters
class to construct a BodyInserter
object and pass it in the body()
method. The BodyInserters
class contains methods to create a BodyInserter
from an Object
, Publisher
, Resource
, FormData
, MultipartData
etc -
public Mono<GithubRepo> createGithubRepository(String username, String token,
RepoRequest createRepoRequest) {
return webClient.post()
.uri("/user/repos")
.body(BodyInserters.fromObject(createRepoRequest))
.header("Authorization", "Basic " + Base64Utils
.encodeToString((username + ":" + token).getBytes(UTF_8)))
.retrieve()
.bodyToMono(GithubRepo.class);
}
Adding Filter Functions
WebClient supports request filtering using an ExchangeFilterFunction
. You can use filter functions to intercept and modify the request in any way. For example, you can use a filter function to add an Authorization
header to every request, or to log the details of every request.
The ExchangeFilterFunction
takes two arguments -
- The
ClientRequest
and - The next
ExchangeFilterFunction
in the filter chain.
It can modify the ClientRequest
and call the next ExchangeFilterFucntion
in the filter chain to proceed to the next filter or return the modified ClientRequest
directly to block the filter chain.
1. Adding Basic Authentication using a filter function
In all the examples above, we are including an Authorization
header for basic authentication with the Github API. Since this is something that is common to all the requests, you can add this logic in a filter function while creating the WebClient
.
The ExchaneFilterFunctions
API already provides a filter for basic authentication. You can use it like this -
WebClient webClient = WebClient.builder()
.baseUrl(GITHUB_API_BASE_URL)
.defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE)
.filter(ExchangeFilterFunctions
.basicAuthentication(username, token))
.build();
Now, You don’t need to add the Authorization
header in every request. The filter function will intercept every WebClient request and add this header.
2. Logging all the requests using a filter function
Let’s see an example of a custom ExchangeFilterFunction
. We’ll write a filter function to intercept and log every request -
WebClient webClient = WebClient.builder()
.baseUrl(GITHUB_API_BASE_URL)
.defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE)
.filter(ExchangeFilterFunctions
.basicAuthentication(username, token))
.filter(logRequest())
.build();
Here is the implementation of the logRequest()
filter function -
private ExchangeFilterFunction logRequest() {
return (clientRequest, next) -> {
logger.info("Request: {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers()
.forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value)));
return next.exchange(clientRequest);
};
}
3. Using ofRequestProcessor() and ofResponseProcessor() factory methods to create filters
ExchangeFilterFunction API provides two factory methods named ofRequestProcessor()
and ofResponseProcessor()
for creating filter functions that intercepts the request and response respectively.
The logRequest()
filter function that we created in the previous section can be created using ofRequestProcessor()
factory method like this -
private ExchangeFilterFunction logRequest() {
ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
logger.info("Request: {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers()
.forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value)));
return Mono.just(clientRequest);
});
}
If you want to intercept the WebClient response, you can use the ofResponseProcessor()
method to create a filter function like this -
private ExchangeFilterFunction logResposneStatus() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
logger.info("Response Status {}", clientResponse.statusCode());
return Mono.just(clientResponse);
});
}
Handling WebClient Errors
The retrieve()
method in WebClient throws a WebClientResponseException
whenever a response with status code 4xx or 5xx is received.
You can customize that using the onStatus()
methods like so -
public Flux<GithubRepo> listGithubRepositories() {
return webClient.get()
.uri("/user/repos?sort={sortField}&direction={sortDirection}",
"updated", "desc")
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse ->
Mono.error(new MyCustomClientException())
)
.onStatus(HttpStatus::is5xxServerError, clientResponse ->
Mono.error(new MyCustomServerException())
)
.bodyToFlux(GithubRepo.class);}
Note that Unlike retrieve()
method, the exchange()
method does not throw exceptions in case of 4xx or 5xx responses. You need to check the status codes yourself and handle them in the way you want to.
Handling WebClientResponseExceptions using an @ExceptionHandler
inside the controller
You can use an @ExceptionHandler
inside your controller to handle WebClientResponseException
and return an appropriate response to the clients like this -
@ExceptionHandler(WebClientResponseException.class)
public ResponseEntity<String> handleWebClientResponseException(WebClientResponseException ex) {
logger.error("Error from WebClient - Status {}, Body {}", ex.getRawStatusCode(), ex.getResponseBodyAsString(), ex);
return ResponseEntity.status(ex.getRawStatusCode()).body(ex.getResponseBodyAsString());
}
Testing Rest APIs using Spring 5 WebTestClient
WebTestClient contains request methods that are similar to WebClient. In addition, it contains methods to check the response status, header and body. You can also use assertion libraries like AssertJ
with WebTestClient.
Check out the following example for learning how to perform rest API tests using WebTestClient -
package com.example;import org.assertj.core.api.Assertions;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;import com.example.payload.GithubRepo;
import com.example.payload.RepoRequest;import reactor.core.publisher.Mono;/**
* @author code.factory
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class Spring5WebclientApplicationTests { @Autowired
private WebTestClient webTestClient; @Test
public void test1CreateGithubRepository() {
RepoRequest repoRequest = new RepoRequest("test-webclient-repository",
"Repository created for testing WebClient"); webTestClient.post().uri("/api/repos")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.body(Mono.just(repoRequest), RepoRequest.class)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
.expectBody()
.jsonPath("$.name").isNotEmpty()
.jsonPath("$.name").isEqualTo("test-webclient-repository");
} @Test
public void test2GetAllGithubRepositories() {
webTestClient.get().uri("/api/repos")
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
.expectBodyList(GithubRepo.class);
} @Test
public void test3GetSingleGithubRepository() {
webTestClient.get().uri("/api/repos/{repo}", "test-webclient-repository")
.exchange()
.expectStatus().isOk()
.expectBody()
.consumeWith(response -> Assertions.assertThat(response.getResponseBody()).isNotNull());
} @Test
public void test4EditGithubRepository() {
RepoRequest newRepoDetails = new RepoRequest("updated-webclient-repository", "Updated name and description");
webTestClient.patch().uri("/api/repos/{repo}", "test-webclient-repository")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.body(Mono.just(newRepoDetails), RepoRequest.class)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
.expectBody()
.jsonPath("$.name").isEqualTo("updated-webclient-repository");
} @Test
public void test5DeleteGithubRepository() {
webTestClient.delete().uri("/api/repos/{repo}", "updated-webclient-repository")
.exchange()
.expectStatus().isOk();
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>spring5-webclient</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring5-webclient</name>
<description>Demo project for Spring Boot 5 WebClient</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency> <dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency> <dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build></project>
application.properties
app.github.username=GIT_USERNAME
app.github.token=GIT_TOKEN
Spring5WebclientApplication.java
package com.example;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;/**
* @author code.factory
*
*/
@SpringBootApplication
public class Spring5WebclientApplication { public static void main(String[] args) {
SpringApplication.run(Spring5WebclientApplication.class, args);
}}
GithubClient.java
package com.example;import com.example.config.AppProperties;
import com.example.payload.RepoRequest;
import com.example.payload.GithubRepo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeFilterFunctions;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;/**
* @author code.factory
*
*/
@Service
public class GithubClient {
private static final String GITHUB_V3_MIME_TYPE = "application/vnd.github.v3+json";
private static final String GITHUB_API_BASE_URL = "https://api.github.com";
private static final String USER_AGENT = "Spring 5 WebClient";
private static final Logger logger = LoggerFactory.getLogger(GithubClient.class); private final WebClient webClient; @Autowired
public GithubClient(AppProperties appProperties) {
this.webClient = WebClient.builder().baseUrl(GITHUB_API_BASE_URL)
.defaultHeader(HttpHeaders.CONTENT_TYPE, GITHUB_V3_MIME_TYPE)
.defaultHeader(HttpHeaders.USER_AGENT, USER_AGENT)
.filter(ExchangeFilterFunctions.basicAuthentication(appProperties.getGithub().getUsername(),
appProperties.getGithub().getToken()))
.filter(logRequest()).build();
} public Flux<GithubRepo> listGithubRepositories() {
return webClient.get().uri("/user/repos?sort={sortField}&direction={sortDirection}", "updated", "desc")
.exchange()
.flatMapMany(clientResponse -> clientResponse.bodyToFlux(GithubRepo.class));
} public Mono<GithubRepo> createGithubRepository(RepoRequest createRepoRequest) {
return webClient.post().uri("/user/repos")
.body(Mono.just(createRepoRequest), RepoRequest.class)
.retrieve()
.bodyToMono(GithubRepo.class);
} public Mono<GithubRepo> getGithubRepository(String owner, String repo) {
return webClient.get().uri("/repos/{owner}/{repo}", owner, repo)
.retrieve()
.bodyToMono(GithubRepo.class);
} public Mono<GithubRepo> editGithubRepository(String owner, String repo, RepoRequest editRepoRequest) {
return webClient.patch().uri("/repos/{owner}/{repo}", owner, repo)
.body(BodyInserters.fromObject(editRepoRequest))
.retrieve()
.bodyToMono(GithubRepo.class);
} public Mono<Void> deleteGithubRepository(String owner, String repo) {
return webClient.delete().uri("/repos/{owner}/{repo}", owner, repo)
.retrieve()
.bodyToMono(Void.class);
} private ExchangeFilterFunction logRequest() {
return (clientRequest, next) -> {
logger.info("Request: {} {}", clientRequest.method(), clientRequest.url());
clientRequest.headers()
.forEach((name, values) -> values.forEach(value -> logger.info("{}={}", name, value)));
return next.exchange(clientRequest);
};
}
}
GithubController.java
package com.example;import com.example.config.AppProperties;
import com.example.payload.RepoRequest;
import com.example.payload.GithubRepo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import javax.validation.Valid;/**
* @author code.factory
*
*/
@RestController
@RequestMapping("/api")
public class GithubController { @Autowired
private GithubClient githubClient; @Autowired
private AppProperties appProperties; private static final Logger logger = LoggerFactory.getLogger(GithubController.class); @GetMapping("/repos")
public Flux<GithubRepo> listGithubRepositories() {
return githubClient.listGithubRepositories();
} @PostMapping("/repos")
public Mono<GithubRepo> createGithubRepository(@RequestBody RepoRequest repoRequest) {
return githubClient.createGithubRepository(repoRequest);
} @GetMapping("/repos/{repo}")
public Mono<GithubRepo> getGithubRepository(@PathVariable String repo) {
return githubClient.getGithubRepository(appProperties.getGithub().getUsername(), repo);
} @PatchMapping("/repos/{repo}")
public Mono<GithubRepo> editGithubRepository(@PathVariable String repo, @Valid @RequestBody RepoRequest repoRequest) {
return githubClient.editGithubRepository(appProperties.getGithub().getUsername(), repo, repoRequest);
} @DeleteMapping("/repos/{repo}")
public Mono<Void> deleteGithubRepository(@PathVariable String repo) {
return githubClient.deleteGithubRepository(appProperties.getGithub().getUsername(), repo);
} @ExceptionHandler(WebClientResponseException.class)
public ResponseEntity<String> handleWebClientResponseException(WebClientResponseException ex) {
logger.error("Error from WebClient - Status {}, Body {}", ex.getRawStatusCode(), ex.getResponseBodyAsString(), ex);
return ResponseEntity.status(ex.getRawStatusCode()).body(ex.getResponseBodyAsString());
}
}
AppProperties.java
package com.example.config;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;/**
* @author code.factory
*
*/
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private final Github github = new Github(); public static class Github {
private String username;
private String token; public String getUsername() {
return username;
} public void setUsername(String username) {
this.username = username;
} public String getToken() {
return token;
} public void setToken(String token) {
this.token = token;
}
} public Github getGithub() {
return github;
}
}
GithubRepo.java
package com.example.payload;import com.fasterxml.jackson.annotation.JsonProperty;/**
* @author code.factory
*
*/
public class GithubRepo {
private Long id; private String name; @JsonProperty("full_name")
private String fullName; private String description; @JsonProperty("private")
private String isPrivate; @JsonProperty("fork")
private String isFork; private String url; @JsonProperty("html_url")
private String htmlUrl; @JsonProperty("git_url")
private String gitUrl; @JsonProperty("forks_count")
private Long forksCount; @JsonProperty("stargazers_count")
private Long stargazersCount; @JsonProperty("watchers_count")
private Long watchersCount; public Long getId() {
return id;
} public void setId(Long id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getFullName() {
return fullName;
} public void setFullName(String fullName) {
this.fullName = fullName;
} public String getDescription() {
return description;
} public void setDescription(String description) {
this.description = description;
} public String getIsPrivate() {
return isPrivate;
} public void setIsPrivate(String isPrivate) {
this.isPrivate = isPrivate;
} public String getIsFork() {
return isFork;
} public void setIsFork(String isFork) {
this.isFork = isFork;
} public String getUrl() {
return url;
} public void setUrl(String url) {
this.url = url;
} public String getHtmlUrl() {
return htmlUrl;
} public void setHtmlUrl(String htmlUrl) {
this.htmlUrl = htmlUrl;
} public String getGitUrl() {
return gitUrl;
} public void setGitUrl(String gitUrl) {
this.gitUrl = gitUrl;
} public Long getForksCount() {
return forksCount;
} public void setForksCount(Long forksCount) {
this.forksCount = forksCount;
} public Long getStargazersCount() {
return stargazersCount;
} public void setStargazersCount(Long stargazersCount) {
this.stargazersCount = stargazersCount;
} public Long getWatchersCount() {
return watchersCount;
} public void setWatchersCount(Long watchersCount) {
this.watchersCount = watchersCount;
}
}
RepoRequest.java
package com.example.payload;import javax.validation.constraints.NotBlank;import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;/**
* @author code.factory
*
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RepoRequest {
@NotBlank
private String name; private String description; @JsonProperty("private")
private Boolean isPrivate; public RepoRequest() { } public RepoRequest(@NotBlank String name, String description) {
this.name = name;
this.description = description;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getDescription() {
return description;
} public void setDescription(String description) {
this.description = description;
} public Boolean getPrivate() {
return isPrivate;
} public void setPrivate(Boolean aPrivate) {
isPrivate = aPrivate;
} @Override
public String toString() {
return "RepoRequest [name=" + name + ", description=" + description + ", isPrivate=" + isPrivate + "]";
}}
Spring5WebclientApplicationTests.java
package com.example;import org.assertj.core.api.Assertions;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.reactive.server.WebTestClient;import com.example.payload.GithubRepo;
import com.example.payload.RepoRequest;import reactor.core.publisher.Mono;/**
* @author code.factory
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class Spring5WebclientApplicationTests { @Autowired
private WebTestClient webTestClient; @Test
public void test1CreateGithubRepository() {
RepoRequest repoRequest = new RepoRequest("test-webclient-repository",
"Repository created for testing WebClient"); webTestClient.post().uri("/api/repos")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.body(Mono.just(repoRequest), RepoRequest.class)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
.expectBody()
.jsonPath("$.name").isNotEmpty()
.jsonPath("$.name").isEqualTo("test-webclient-repository");
} @Test
public void test2GetAllGithubRepositories() {
webTestClient.get().uri("/api/repos")
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
.expectBodyList(GithubRepo.class);
} @Test
public void test3GetSingleGithubRepository() {
webTestClient.get().uri("/api/repos/{repo}", "test-webclient-repository")
.exchange()
.expectStatus().isOk()
.expectBody()
.consumeWith(response -> Assertions.assertThat(response.getResponseBody()).isNotNull());
} @Test
public void test4EditGithubRepository() {
RepoRequest newRepoDetails = new RepoRequest("updated-webclient-repository", "Updated name and description");
webTestClient.patch().uri("/api/repos/{repo}", "test-webclient-repository")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.body(Mono.just(newRepoDetails), RepoRequest.class)
.exchange()
.expectStatus().isOk()
.expectHeader().contentType(MediaType.APPLICATION_JSON_UTF8)
.expectBody()
.jsonPath("$.name").isEqualTo("updated-webclient-repository");
} @Test
public void test5DeleteGithubRepository() {
webTestClient.delete().uri("/api/repos/{repo}", "updated-webclient-repository")
.exchange()
.expectStatus().isOk();
}
}