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

  1. Go to http://start.spring.io.

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 -

  1. The ClientRequest and

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