diff --git a/spring-boot/spring-boot-testing/README.md b/spring-boot/spring-boot-testing/README.md index 9b5a2e2..259931a 100644 --- a/spring-boot/spring-boot-testing/README.md +++ b/spring-boot/spring-boot-testing/README.md @@ -3,3 +3,4 @@ ## Blog Posts * [Structuring and Testing Modules and Layers with Spring Boot](https://reflectoring.io/testing-verticals-and-layers-spring-boot/) * [All You Need To Know About Unit Testing with Spring Boot](https://reflectoring.io/unit-testing-spring-boot/) +* [All You Need To Know About Testing Web Controllers with Spring Boot](https://reflectoring.io/spring-boot-web-controller-test/) diff --git a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/UserRepository.java b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/UserRepository.java deleted file mode 100644 index a6a599e..0000000 --- a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/UserRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.reflectoring.testing; - - -import org.springframework.data.repository.CrudRepository; - -public interface UserRepository extends CrudRepository { -} diff --git a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/RegisterUseCase.java b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/domain/RegisterUseCase.java similarity index 56% rename from spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/RegisterUseCase.java rename to spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/domain/RegisterUseCase.java index dc2bba3..7337099 100644 --- a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/RegisterUseCase.java +++ b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/domain/RegisterUseCase.java @@ -1,4 +1,4 @@ -package io.reflectoring.testing; +package io.reflectoring.testing.domain; import java.time.LocalDateTime; @@ -9,11 +9,11 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor public class RegisterUseCase { - private final UserRepository userRepository; + private final SaveUserPort saveUserPort; - public User registerUser(User user) { + public Long registerUser(User user, boolean sendWelcomeMail) { user.setRegistrationDate(LocalDateTime.now()); - return userRepository.save(user); + return saveUserPort.saveUser(user); } } diff --git a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/domain/SaveUserPort.java b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/domain/SaveUserPort.java new file mode 100644 index 0000000..38c6341 --- /dev/null +++ b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/domain/SaveUserPort.java @@ -0,0 +1,7 @@ +package io.reflectoring.testing.domain; + +public interface SaveUserPort { + + Long saveUser(User user); + +} diff --git a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/User.java b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/domain/User.java similarity index 91% rename from spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/User.java rename to spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/domain/User.java index d1bc6d7..4d7e663 100644 --- a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/User.java +++ b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/domain/User.java @@ -1,4 +1,4 @@ -package io.reflectoring.testing; +package io.reflectoring.testing.domain; import javax.persistence.Entity; import javax.persistence.Id; diff --git a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/persistence/PersistenceAdapter.java b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/persistence/PersistenceAdapter.java new file mode 100644 index 0000000..ccdc2ad --- /dev/null +++ b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/persistence/PersistenceAdapter.java @@ -0,0 +1,22 @@ +package io.reflectoring.testing.persistence; + +import io.reflectoring.testing.domain.SaveUserPort; +import io.reflectoring.testing.domain.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class PersistenceAdapter implements SaveUserPort { + + private final UserEntityRepository userEntityRepository; + + @Override + public Long saveUser(User user) { + UserEntity userEntity = new UserEntity( + user.getName(), + user.getEmail()); + UserEntity savedUserEntity = userEntityRepository.save(userEntity); + return savedUserEntity.getId(); + } +} diff --git a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/persistence/UserEntity.java b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/persistence/UserEntity.java new file mode 100644 index 0000000..227ae1d --- /dev/null +++ b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/persistence/UserEntity.java @@ -0,0 +1,26 @@ +package io.reflectoring.testing.persistence; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import java.time.LocalDateTime; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Entity +@Data +@AllArgsConstructor +public class UserEntity { + + @Id + private Long id; + private String name; + private String email; + private LocalDateTime registrationDate; + + public UserEntity(String name, String email) { + this.name = name; + this.email = email; + } +} diff --git a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/persistence/UserEntityRepository.java b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/persistence/UserEntityRepository.java new file mode 100644 index 0000000..ec02b16 --- /dev/null +++ b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/persistence/UserEntityRepository.java @@ -0,0 +1,7 @@ +package io.reflectoring.testing.persistence; + + +import org.springframework.data.repository.CrudRepository; + +public interface UserEntityRepository extends CrudRepository { +} diff --git a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/ControllerExceptionHandler.java b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/ControllerExceptionHandler.java new file mode 100644 index 0000000..f97e764 --- /dev/null +++ b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/ControllerExceptionHandler.java @@ -0,0 +1,26 @@ +package io.reflectoring.testing.web; + +import org.springframework.http.HttpStatus; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ControllerAdvice +public class ControllerExceptionHandler { + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseBody + public ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + ErrorResult errorResult = new ErrorResult(); + for (FieldError fieldError : e.getBindingResult().getFieldErrors()) { + errorResult.getFieldErrors() + .add(new FieldValidationError(fieldError.getField(), fieldError.getDefaultMessage())); + } + return errorResult; + } + +} diff --git a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/ErrorResult.java b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/ErrorResult.java new file mode 100644 index 0000000..569dcbf --- /dev/null +++ b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/ErrorResult.java @@ -0,0 +1,18 @@ +package io.reflectoring.testing.web; + +import java.util.ArrayList; +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.NoArgsConstructor; +import lombok.Value; + +@Value +@NoArgsConstructor +public class ErrorResult { + private final List fieldErrors = new ArrayList<>(); + + ErrorResult(String field, String message) { + this.fieldErrors.add(new FieldValidationError(field, message)); + } +} \ No newline at end of file diff --git a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/FieldValidationError.java b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/FieldValidationError.java new file mode 100644 index 0000000..3f1f07b --- /dev/null +++ b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/FieldValidationError.java @@ -0,0 +1,18 @@ +package io.reflectoring.testing.web; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +public class FieldValidationError { + + private String field; + + private String message; + + public FieldValidationError(@JsonProperty("field") String field, @JsonProperty("message") String message) { + this.field = field; + this.message = message; + } +} \ No newline at end of file diff --git a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/RegisterMvcController.java b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/RegisterMvcController.java new file mode 100644 index 0000000..a25ea9d --- /dev/null +++ b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/RegisterMvcController.java @@ -0,0 +1,34 @@ +package io.reflectoring.testing.web; + +import io.reflectoring.testing.domain.RegisterUseCase; +import io.reflectoring.testing.domain.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; + +@Controller +@RequiredArgsConstructor +class RegisterMvcController { + + private final RegisterUseCase registerUseCase; + + @PostMapping("/mvc/register/") + ModelAndView register(@ModelAttribute("user") UserResource userResource){ + + User user = new User( + userResource.getName(), + userResource.getEmail()); + + registerUseCase.registerUser(user, false); + + ModelAndView modelAndView = new ModelAndView("registration_success.html"); + modelAndView.addObject("username", user.getName()); + + return modelAndView; + } + +} diff --git a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/RegisterRestController.java b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/RegisterRestController.java new file mode 100644 index 0000000..20232f0 --- /dev/null +++ b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/RegisterRestController.java @@ -0,0 +1,36 @@ +package io.reflectoring.testing.web; + +import javax.validation.Valid; + +import io.reflectoring.testing.domain.RegisterUseCase; +import io.reflectoring.testing.domain.User; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +class RegisterRestController { + + private final RegisterUseCase registerUseCase; + + @PostMapping("/forums/{forumId}/register") + UserResource register( + @PathVariable("forumId") Long forumId, + @Valid @RequestBody UserResource userResource, + @RequestParam("sendWelcomeMail") boolean sendWelcomeMail) { + + User user = new User( + userResource.getName(), + userResource.getEmail()); + Long userId = registerUseCase.registerUser(user, sendWelcomeMail); + + return new UserResource( + user.getName(), + user.getEmail()); + } + +} diff --git a/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/UserResource.java b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/UserResource.java new file mode 100644 index 0000000..880bbe4 --- /dev/null +++ b/spring-boot/spring-boot-testing/src/main/java/io/reflectoring/testing/web/UserResource.java @@ -0,0 +1,28 @@ +package io.reflectoring.testing.web; + +import javax.validation.constraints.NotNull; + +import java.time.LocalDateTime; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Value; + +@Value +public class UserResource { + + @NotNull + private final String name; + + @NotNull + private final String email; + + private LocalDateTime registrationDate; + + public UserResource( + @JsonProperty("name") String name, + @JsonProperty("email") String email) { + this.name = name; + this.email = email; + this.registrationDate = null; + } +} diff --git a/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/RegisterUseCaseTest.java b/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/RegisterUseCaseTest.java index 7ae7076..05d20e8 100644 --- a/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/RegisterUseCaseTest.java +++ b/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/RegisterUseCaseTest.java @@ -1,11 +1,14 @@ package io.reflectoring.testing; +import io.reflectoring.testing.domain.RegisterUseCase; +import io.reflectoring.testing.domain.SaveUserPort; +import io.reflectoring.testing.domain.User; 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.junit.jupiter.MockitoExtension; -import static io.reflectoring.testing.UserAssert.assertThat; +import static org.assertj.core.api.Java6Assertions.*; import static org.mockito.AdditionalAnswers.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -14,21 +17,21 @@ import static org.mockito.Mockito.*; class RegisterUseCaseTest { @Mock - private UserRepository userRepository; + private SaveUserPort saveUserPort; private RegisterUseCase registerUseCase; @BeforeEach void initUseCase() { - registerUseCase = new RegisterUseCase(userRepository); + registerUseCase = new RegisterUseCase(saveUserPort); } @Test void savedUserHasRegistrationDate() { User user = new User("zaphod", "zaphod@mail.com"); - when(userRepository.save(any(User.class))).then(returnsFirstArg()); - User savedUser = registerUseCase.registerUser(user); - assertThat(savedUser).hasRegistrationDate(); + when(saveUserPort.saveUser(any(User.class))).then(returnsFirstArg()); + Long userId = registerUseCase.registerUser(user, false); + assertThat(userId).isNotNull(); } } \ No newline at end of file diff --git a/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/UserAssert.java b/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/UserAssert.java index 0ebc952..07098a9 100644 --- a/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/UserAssert.java +++ b/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/UserAssert.java @@ -1,5 +1,6 @@ package io.reflectoring.testing; +import io.reflectoring.testing.domain.User; import org.assertj.core.api.AbstractAssert; public class UserAssert extends AbstractAssert { diff --git a/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/web/RegisterRestControllerTest.java b/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/web/RegisterRestControllerTest.java new file mode 100644 index 0000000..a06cd87 --- /dev/null +++ b/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/web/RegisterRestControllerTest.java @@ -0,0 +1,160 @@ +package io.reflectoring.testing.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.reflectoring.testing.domain.RegisterUseCase; +import io.reflectoring.testing.domain.User; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import static io.reflectoring.testing.web.ResponseBodyMatchers.*; +import static org.assertj.core.api.Java6Assertions.*; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@ExtendWith(SpringExtension.class) +@WebMvcTest(controllers = RegisterRestController.class) +class RegisterRestControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockBean + private RegisterUseCase registerUseCase; + + @Test + void whenValidInput_thenReturns200() throws Exception { + + UserResource user = new UserResource("Zaphod", "zaphod@galaxy.net"); + + mockMvc.perform(post("/forums/{forumId}/register", 42L) + .contentType("application/json") + .param("sendWelcomeMail", "true") + .content(objectMapper.writeValueAsString(user))) + .andExpect(status().isOk()); + + } + + @Test + void whenValidInput_thenMapsToBusinessModel() throws Exception { + + UserResource user = new UserResource("Zaphod", "zaphod@galaxy.net"); + + mockMvc.perform(post("/forums/{forumId}/register", 42L) + .contentType("application/json") + .param("sendWelcomeMail", "true") + .content(objectMapper.writeValueAsString(user))) + .andExpect(status().isOk()); + + ArgumentCaptor userCaptor = ArgumentCaptor.forClass(User.class); + verify(registerUseCase, times(1)).registerUser(userCaptor.capture(), eq(true)); + assertThat(userCaptor.getValue().getName()).isEqualTo("Zaphod"); + assertThat(userCaptor.getValue().getEmail()).isEqualTo("zaphod@galaxy.net"); + + } + + @Test + void whenValidInput_thenReturnsUserResource() throws Exception { + + UserResource user = new UserResource("Zaphod", "zaphod@galaxy.net"); + + MvcResult mvcResult = mockMvc.perform(post("/forums/{forumId}/register", 42L) + .contentType("application/json") + .param("sendWelcomeMail", "true") + .content(objectMapper.writeValueAsString(user))) + .andExpect(status().isOk()) + .andReturn(); + + UserResource expectedResponseBody = user; + String actualResponseBody = mvcResult.getResponse().getContentAsString(); + assertThat(objectMapper.writeValueAsString(expectedResponseBody)) + .isEqualToIgnoringWhitespace(actualResponseBody); + + } + + @Test + void whenValidInput_thenReturnsUserResource_withFluentApi() throws Exception { + + UserResource user = new UserResource("Zaphod", "zaphod@galaxy.net"); + UserResource expectedResponseBody = user; + + mockMvc.perform(post("/forums/{forumId}/register", 42L) + .contentType("application/json") + .param("sendWelcomeMail", "true") + .content(objectMapper.writeValueAsString(user))) + .andExpect(status().isOk()) + .andExpect(responseBody().containsObjectAsJson(expectedResponseBody, UserResource.class)); + } + + @Test + void whenValidInput_thenReturnsUserResource_withTypedAssertion() throws Exception { + + UserResource user = new UserResource("Zaphod", "zaphod@galaxy.net"); + + MvcResult mvcResult = mockMvc.perform(post("/forums/{forumId}/register", 42L) + .contentType("application/json") + .param("sendWelcomeMail", "true") + .content(objectMapper.writeValueAsString(user))) + .andExpect(status().isOk()) + .andReturn(); + + UserResource expectedResponseBody = user; + UserResource actualResponseBody = objectMapper.readValue(mvcResult.getResponse().getContentAsString(), UserResource.class); + assertThat(expectedResponseBody.getName()).isEqualTo(actualResponseBody.getName()); + assertThat(expectedResponseBody.getEmail()).isEqualTo(actualResponseBody.getEmail()); + + } + + @Test + void whenNullValue_thenReturns400() throws Exception { + + UserResource user = new UserResource(null, "zaphod@galaxy.net"); + + mockMvc.perform(post("/forums/{forumId}/register", 42L) + .contentType("application/json") + .param("sendWelcomeMail", "true") + .content(objectMapper.writeValueAsString(user))) + .andExpect(status().isBadRequest()); + } + + @Test + void whenNullValue_thenReturns400AndErrorResult() throws Exception { + + UserResource user = new UserResource(null, "zaphod@galaxy.net"); + + MvcResult mvcResult = mockMvc.perform(post("/forums/{forumId}/register", 42L) + .contentType("application/json") + .param("sendWelcomeMail", "true") + .content(objectMapper.writeValueAsString(user))) + .andExpect(status().isBadRequest()) + .andReturn(); + + ErrorResult expectedErrorResponse = new ErrorResult("name", "must not be null"); + String actualResponseBody = mvcResult.getResponse().getContentAsString(); + String expectedResponseBody = objectMapper.writeValueAsString(expectedErrorResponse); + assertThat(expectedResponseBody).isEqualToIgnoringWhitespace(actualResponseBody); + } + + @Test + void whenNullValue_thenReturns400AndErrorResult_withFluentApi() throws Exception { + + UserResource user = new UserResource(null, "zaphod@galaxy.net"); + + mockMvc.perform(post("/forums/{forumId}/register", 42L) + .contentType("application/json") + .param("sendWelcomeMail", "true") + .content(objectMapper.writeValueAsString(user))) + .andExpect(status().isBadRequest()) + .andExpect(responseBody().containsErrorMessageForField("name", "must not be null")); + } + +} \ No newline at end of file diff --git a/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/web/ResponseBodyMatchers.java b/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/web/ResponseBodyMatchers.java new file mode 100644 index 0000000..b0c7711 --- /dev/null +++ b/spring-boot/spring-boot-testing/src/test/java/io/reflectoring/testing/web/ResponseBodyMatchers.java @@ -0,0 +1,45 @@ +package io.reflectoring.testing.web; + + +import java.util.List; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.test.web.servlet.ResultMatcher; +import static org.assertj.core.api.Java6Assertions.*; + +public class ResponseBodyMatchers { + + private ObjectMapper objectMapper = new ObjectMapper(); + + public ResultMatcher containsObjectAsJson(Object expectedObject, Class targetClass) { + return mvcResult -> { + String json = mvcResult.getResponse().getContentAsString(); + T actualObject = objectMapper.readValue(json, targetClass); + assertThat(expectedObject).isEqualToComparingFieldByField(actualObject); + }; + } + + public ResultMatcher containsErrorMessageForField(String expectedFieldName, String expectedMessage) { + return mvcResult -> { + String json = mvcResult.getResponse().getContentAsString(); + ErrorResult errorResult = objectMapper.readValue(json, ErrorResult.class); + List fieldErrors = errorResult.getFieldErrors().stream() + .filter(fieldError -> fieldError.getField().equals(expectedFieldName)) + .filter(fieldError -> fieldError.getMessage().equals(expectedMessage)) + .collect(Collectors.toList()); + + assertThat(fieldErrors) + .hasSize(1) + .withFailMessage("expecting exactly 1 error message with field name '%s' and message '%s'", + expectedFieldName, + expectedMessage); + }; + } + + + static ResponseBodyMatchers responseBody() { + return new ResponseBodyMatchers(); + } + +}