diff --git a/spring-5-reactive-client/pom.xml b/spring-5-reactive-client/pom.xml
index 70771f6832..9e574b2196 100644
--- a/spring-5-reactive-client/pom.xml
+++ b/spring-5-reactive-client/pom.xml
@@ -54,6 +54,18 @@
org.apache.commons
commons-lang3
+
+
+ com.squareup.okhttp3
+ okhttp
+ 4.0.1
+
+
+ com.squareup.okhttp3
+ mockwebserver
+ 4.0.1
+ test
+
@@ -88,14 +100,25 @@
org.projectlombok
lombok
-
-
+
+ org.mockito
+ mockito-junit-jupiter
+ 2.23.0
+ test
+
+
+ io.projectreactor
+ reactor-test
+ 3.2.10.RELEASE
+ test
+
org.eclipse.jetty
jetty-reactive-httpclient
${jetty-reactive-httpclient.version}
test
+
@@ -108,6 +131,29 @@
JAR
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ 1.8
+ 1.8
+
+
+
+ maven-surefire-plugin
+ 2.22.0
+
+
+ maven-surefire-plugin
+ 2.19.1
+
+
+ org.junit.platform
+ junit-platform-surefire-provider
+ 1.0.1
+
+
+
diff --git a/spring-5-reactive-client/src/main/java/com/baeldung/reactive/enums/Role.java b/spring-5-reactive-client/src/main/java/com/baeldung/reactive/enums/Role.java
new file mode 100644
index 0000000000..6b11f14734
--- /dev/null
+++ b/spring-5-reactive-client/src/main/java/com/baeldung/reactive/enums/Role.java
@@ -0,0 +1,5 @@
+package com.baeldung.reactive.enums;
+
+public enum Role {
+ ENGINEER, SENIOR_ENGINEER, LEAD_ENGINEER
+}
diff --git a/spring-5-reactive-client/src/main/java/com/baeldung/reactive/model/Employee.java b/spring-5-reactive-client/src/main/java/com/baeldung/reactive/model/Employee.java
new file mode 100644
index 0000000000..6a8daaf6de
--- /dev/null
+++ b/spring-5-reactive-client/src/main/java/com/baeldung/reactive/model/Employee.java
@@ -0,0 +1,64 @@
+package com.baeldung.reactive.model;
+
+
+import com.baeldung.reactive.enums.Role;
+
+public class Employee {
+ private Integer employeeId;
+ private String firstName;
+ private String lastName;
+ private Integer age;
+ private Role role;
+
+ public Employee() {
+ }
+
+ public Employee(Integer employeeId, String firstName, String lastName, Integer age, Role role) {
+ this.employeeId = employeeId;
+ this.firstName = firstName;
+ this.lastName = lastName;
+ this.age = age;
+ this.role = role;
+ }
+
+ public Integer getEmployeeId() {
+ return employeeId;
+ }
+
+ public void setEmployeeId(Integer employeeId) {
+ this.employeeId = employeeId;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public Role getRole() {
+ return role;
+ }
+
+ public void setRole(Role role) {
+ this.role = role;
+ }
+}
+
diff --git a/spring-5-reactive-client/src/main/java/com/baeldung/reactive/service/EmployeeService.java b/spring-5-reactive-client/src/main/java/com/baeldung/reactive/service/EmployeeService.java
new file mode 100644
index 0000000000..b841dbfe3f
--- /dev/null
+++ b/spring-5-reactive-client/src/main/java/com/baeldung/reactive/service/EmployeeService.java
@@ -0,0 +1,55 @@
+package com.baeldung.reactive.service;
+import com.baeldung.reactive.model.Employee;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+
+public class EmployeeService {
+
+ private WebClient webClient;
+ public static String PATH_PARAM_BY_ID = "/employee/{id}";
+ public static String ADD_EMPLOYEE = "/employee";
+
+ public EmployeeService(WebClient webClient) {
+ this.webClient = webClient;
+ }
+
+ public EmployeeService(String baseUrl) {
+ this.webClient = WebClient.create(baseUrl);
+ }
+
+ public Mono getEmployeeById(Integer employeeId) {
+ return webClient
+ .get()
+ .uri(PATH_PARAM_BY_ID, employeeId)
+ .retrieve()
+ .bodyToMono(Employee.class);
+ }
+
+ public Mono addNewEmployee(Employee newEmployee) {
+
+ return webClient
+ .post()
+ .uri(ADD_EMPLOYEE)
+ .syncBody(newEmployee)
+ .retrieve().
+ bodyToMono(Employee.class);
+ }
+
+ public Mono updateEmployee(Integer employeeId, Employee updateEmployee) {
+
+ return webClient
+ .put()
+ .uri(PATH_PARAM_BY_ID,employeeId)
+ .syncBody(updateEmployee)
+ .retrieve()
+ .bodyToMono(Employee.class);
+ }
+
+ public Mono deleteEmployeeById(Integer employeeId) {
+ return webClient
+ .delete()
+ .uri(PATH_PARAM_BY_ID,employeeId)
+ .retrieve()
+ .bodyToMono(String.class);
+ }
+}
diff --git a/spring-5-reactive-client/src/test/java/com/baeldung/reactive/service/EmployeeServiceIntegrationTest.java b/spring-5-reactive-client/src/test/java/com/baeldung/reactive/service/EmployeeServiceIntegrationTest.java
new file mode 100644
index 0000000000..f5d9529ada
--- /dev/null
+++ b/spring-5-reactive-client/src/test/java/com/baeldung/reactive/service/EmployeeServiceIntegrationTest.java
@@ -0,0 +1,121 @@
+package com.baeldung.reactive.service;
+
+import com.baeldung.reactive.model.Employee;
+import com.baeldung.reactive.enums.Role;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import okhttp3.mockwebserver.MockResponse;
+import okhttp3.mockwebserver.MockWebServer;
+import okhttp3.mockwebserver.RecordedRequest;
+import org.junit.Rule;
+import org.junit.jupiter.api.*;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class EmployeeServiceIntegrationTest {
+
+ public static MockWebServer mockBackEnd;
+ private EmployeeService employeeService;
+ private ObjectMapper MAPPER = new ObjectMapper();
+
+ @BeforeAll
+ static void setUp() throws IOException {
+ mockBackEnd = new MockWebServer();
+ mockBackEnd.start();
+ }
+
+ @AfterAll
+ static void tearDown() throws IOException {
+ mockBackEnd.shutdown();
+ }
+
+ @BeforeEach
+ void initialize() {
+
+ String baseUrl = String.format("http://localhost:%s", mockBackEnd.getPort());
+ employeeService = new EmployeeService(baseUrl);
+ }
+
+ @Test
+ void getEmployeeById() throws Exception {
+
+ Employee mockEmployee = new Employee(100, "Adam", "Sandler", 32, Role.LEAD_ENGINEER);
+ mockBackEnd.enqueue(new MockResponse().setBody(MAPPER.writeValueAsString(mockEmployee))
+ .addHeader("Content-Type", "application/json"));
+
+ Mono employeeMono = employeeService.getEmployeeById(100);
+
+ StepVerifier.create(employeeMono)
+ .expectNextMatches(employee -> employee.getRole().equals(Role.LEAD_ENGINEER))
+ .verifyComplete();
+
+ RecordedRequest recordedRequest = mockBackEnd.takeRequest();
+ assertEquals("GET", recordedRequest.getMethod());
+ assertEquals("/employee/100", recordedRequest.getPath());
+ }
+
+ @Test
+ void addNewEmployee() throws Exception {
+
+ Employee newEmployee = new Employee(null, "Adam", "Sandler", 32, Role.LEAD_ENGINEER);
+ Employee webClientResponse = new Employee(100, "Adam", "Sandler", 32, Role.LEAD_ENGINEER);
+ mockBackEnd.enqueue(new MockResponse().setBody(MAPPER.writeValueAsString(webClientResponse))
+ .addHeader("Content-Type", "application/json"));
+
+ Mono employeeMono = employeeService.addNewEmployee(newEmployee);
+
+ StepVerifier.create(employeeMono)
+ .expectNextMatches(employee -> employee.getEmployeeId().equals(100))
+ .verifyComplete();
+
+ RecordedRequest recordedRequest = mockBackEnd.takeRequest();
+ assertEquals("POST", recordedRequest.getMethod());
+ assertEquals("/employee", recordedRequest.getPath());
+ }
+
+ @Test
+ void updateEmployee() throws Exception {
+
+ Integer newAge = 33;
+ String newLastName = "Sandler New";
+ Employee updateEmployee = new Employee(100, "Adam", newLastName, newAge, Role.LEAD_ENGINEER);
+ mockBackEnd.enqueue(new MockResponse().setBody(MAPPER.writeValueAsString(updateEmployee))
+ .addHeader("Content-Type", "application/json"));
+
+ Mono updatedEmploye = employeeService.updateEmployee(100, updateEmployee);
+
+ StepVerifier.create(updatedEmploye)
+ .expectNextMatches(employee -> employee.getLastName().equals(newLastName) && employee.getAge() == newAge)
+ .verifyComplete();
+
+ RecordedRequest recordedRequest = mockBackEnd.takeRequest();
+ assertEquals("PUT", recordedRequest.getMethod());
+ assertEquals("/employee/100", recordedRequest.getPath());
+
+ }
+
+
+ @Test
+ void deleteEmployee() throws Exception {
+
+ String responseMessage = "Employee Deleted SuccessFully";
+ Integer employeeId = 100;
+ mockBackEnd.enqueue(new MockResponse().setBody(MAPPER.writeValueAsString(responseMessage))
+ .addHeader("Content-Type", "application/json"));
+
+ Mono deletedEmployee = employeeService.deleteEmployeeById(employeeId);
+
+ StepVerifier.create(deletedEmployee)
+ .expectNext("\"Employee Deleted SuccessFully\"")
+ .verifyComplete();
+
+ RecordedRequest recordedRequest = mockBackEnd.takeRequest();
+ assertEquals("DELETE", recordedRequest.getMethod());
+ assertEquals("/employee/100", recordedRequest.getPath());
+ }
+
+}
\ No newline at end of file
diff --git a/spring-5-reactive-client/src/test/java/com/baeldung/reactive/service/EmployeeServiceUnitTest.java b/spring-5-reactive-client/src/test/java/com/baeldung/reactive/service/EmployeeServiceUnitTest.java
new file mode 100644
index 0000000000..1d1a8fd2e4
--- /dev/null
+++ b/spring-5-reactive-client/src/test/java/com/baeldung/reactive/service/EmployeeServiceUnitTest.java
@@ -0,0 +1,114 @@
+package com.baeldung.reactive.service;
+
+
+import com.baeldung.reactive.model.Employee;
+import com.baeldung.reactive.enums.Role;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.exceptions.base.MockitoException;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class EmployeeServiceUnitTest {
+
+ EmployeeService employeeService;
+ @Mock
+ private WebClient webClientMock;
+ @Mock
+ private WebClient.RequestHeadersSpec requestHeadersMock;
+ @Mock
+ private WebClient.RequestHeadersUriSpec requestHeadersUriMock;
+ @Mock
+ private WebClient.RequestBodySpec requestBodyMock;
+ @Mock
+ private WebClient.RequestBodyUriSpec requestBodyUriMock;
+ @Mock
+ private WebClient.ResponseSpec responseMock;
+
+ @BeforeEach
+ void setUp() {
+ employeeService = new EmployeeService(webClientMock);
+ }
+
+ @Test
+ void givenEmployeeId_whenGetEmployeeById_thenReturnEmployee() {
+
+ Integer employeeId = 100;
+ Employee mockEmployee = new Employee(100, "Adam", "Sandler", 32, Role.LEAD_ENGINEER);
+ when(webClientMock.get()).thenReturn(requestHeadersUriMock);
+ when(requestHeadersUriMock.uri("/employee/{id}", employeeId)).thenReturn(requestHeadersMock);
+ when(requestHeadersMock.retrieve()).thenReturn(responseMock);
+ when(responseMock.bodyToMono(Employee.class)).thenReturn(Mono.just(mockEmployee));
+
+ Mono employeeMono = employeeService.getEmployeeById(employeeId);
+
+ StepVerifier.create(employeeMono)
+ .expectNextMatches(employee -> employee.getRole().equals(Role.LEAD_ENGINEER))
+ .verifyComplete();
+ }
+
+ @Test
+ void givenEmployee_whenAddEmployee_thenAddNewEmployee() {
+
+ Employee newEmployee = new Employee(null, "Adam", "Sandler", 32, Role.LEAD_ENGINEER);
+ Employee webClientResponse = new Employee(100, "Adam", "Sandler", 32, Role.LEAD_ENGINEER);
+ when(webClientMock.post()).thenReturn(requestBodyUriMock);
+ when(requestBodyUriMock.uri(EmployeeService.ADD_EMPLOYEE)).thenReturn(requestBodyMock);
+ when(requestBodyMock.syncBody(newEmployee)).thenReturn(requestHeadersMock);
+ when(requestHeadersMock.retrieve()).thenReturn(responseMock);
+ when(responseMock.bodyToMono(Employee.class)).thenReturn(Mono.just(webClientResponse));
+
+ Mono employeeMono = employeeService.addNewEmployee(newEmployee);
+
+ StepVerifier.create(employeeMono)
+ .expectNextMatches(employee -> employee.getEmployeeId().equals(100))
+ .verifyComplete();
+ }
+
+ @Test
+ void givenEmployee_whenupdateEmployee_thenUpdatedEmployee() {
+
+ Integer newAge = 33;
+ String newLastName = "Sandler New";
+ Employee updateEmployee = new Employee(100, "Adam", newLastName, newAge, Role.LEAD_ENGINEER);
+ when(webClientMock.put()).thenReturn(requestBodyUriMock);
+ when(requestBodyUriMock.uri(EmployeeService.PATH_PARAM_BY_ID, 100)).thenReturn(requestBodyMock);
+ when(requestBodyMock.syncBody(updateEmployee)).thenReturn(requestHeadersMock);
+ when(requestHeadersMock.retrieve()).thenReturn(responseMock);
+ when(responseMock.bodyToMono(Employee.class)).thenReturn(Mono.just(updateEmployee));
+
+ Mono updatedEmployee = employeeService.updateEmployee(100, updateEmployee);
+
+ StepVerifier.create(updatedEmployee)
+ .expectNextMatches(employee -> employee.getLastName().equals(newLastName) && employee.getAge() == newAge)
+ .verifyComplete();
+
+ }
+
+ @Test
+ void givenEmployee_whenDeleteEmployeeById_thenDeleteSuccessful() {
+
+ String responseMessage = "Employee Deleted SuccessFully";
+ when(webClientMock.delete()).thenReturn(requestHeadersUriMock);
+ when(requestHeadersUriMock.uri(EmployeeService.PATH_PARAM_BY_ID, 100)).thenReturn(requestHeadersMock);
+ when(requestHeadersMock.retrieve()).thenReturn(responseMock);
+ when(responseMock.bodyToMono(String.class)).thenReturn(Mono.just(responseMessage));
+
+ Mono deletedEmployee = employeeService.deleteEmployeeById(100);
+
+ StepVerifier.create(deletedEmployee)
+ .expectNext(responseMessage)
+ .verifyComplete();
+ }
+}
\ No newline at end of file