原文: https://howtodoinjava.com/spring-webflux/webfluxtest-with-webtestclient/

学习使用@WebFluxTest注解和WebTestClient来对 spring boot webflux 控制器进行单元测试,该测试用于通过 Junit 5 测试 webflux 端点。

1. 使用WebTestClient@WebFluxTest

1.1. Maven 依赖

添加reactor-test依赖。

pom.xml

  1. <dependency>
  2. <groupId>io.projectreactor</groupId>
  3. <artifactId>reactor-test</artifactId>
  4. <scope>test</scope>
  5. </dependency>

1.2. @WebFluxTest注解

它需要完全自动配置,而仅应用与 WebFlux 测试相关的配置(例如@Controller@ControllerAdvice@JsonComponentConverterWebFluxConfigurer bean,但没有@Component@Service@Repository bean)。

默认情况下,用@WebFluxTest注解的测试还将自动配置WebTestClient

通常,@WebFluxTest@MockBean@Import结合使用以创建@Controller bean 所需的任何协作者。

要编写需要完整应用程序上下文的集成测试,请考虑将@SpringBootTest@AutoConfigureWebTestClient结合使用。

1.3. WebTestClient

它是用于测试 Web 服务器的非阻塞式响应客户端,该客户端内部使用响应WebClient来执行请求,并提供流利的 API 来验证响应。

它可以通过 HTTP 连接到任何服务器,或使用模拟请求和响应对象直接绑定到 WebFlux 应用程序,而无需 HTTP 服务器。

WebTestClientMockMvc相似。 这些测试 Web 客户端之间的唯一区别是WebTestClient旨在测试 WebFlux 端点。

2. 测试 webflux 控制器

2.1. 用于 Webflux 控制器的 Junit 5 测试

在给定的示例中,我们正在测试EmployeeController类,该类包含用于 CRUD 操作的指令方法。

EmployeeControllerTest.java

  1. import static org.mockito.Mockito.times;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import org.junit.jupiter.api.Test;
  5. import org.junit.jupiter.api.extension.ExtendWith;
  6. import org.mockito.Mockito;
  7. import org.springframework.beans.factory.annotation.Autowired;
  8. import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest;
  9. import org.springframework.boot.test.mock.mockito.MockBean;
  10. import org.springframework.context.annotation.Import;
  11. import org.springframework.http.HttpHeaders;
  12. import org.springframework.http.MediaType;
  13. import org.springframework.test.context.junit.jupiter.SpringExtension;
  14. import org.springframework.test.web.reactive.server.WebTestClient;
  15. import org.springframework.web.reactive.function.BodyInserters;
  16. import com.howtodoinjava.demo.controller.EmployeeController;
  17. import com.howtodoinjava.demo.dao.EmployeeRepository;
  18. import com.howtodoinjava.demo.model.Employee;
  19. import com.howtodoinjava.demo.service.EmployeeService;
  20. import reactor.core.publisher.Flux;
  21. import reactor.core.publisher.Mono;
  22. @ExtendWith(SpringExtension.class)
  23. @WebFluxTest(controllers = EmployeeController.class)
  24. @Import(EmployeeService.class)
  25. public class EmployeeControllerTest
  26. {
  27. @MockBean
  28. EmployeeRepository repository;
  29. @Autowired
  30. private WebTestClient webClient;
  31. @Test
  32. void testCreateEmployee() {
  33. Employee employee = new Employee();
  34. employee.setId(1);
  35. employee.setName("Test");
  36. employee.setSalary(1000);
  37. Mockito.when(repository.save(employee)).thenReturn(Mono.just(employee));
  38. webClient.post()
  39. .uri("/create")
  40. .contentType(MediaType.APPLICATION_JSON)
  41. .body(BodyInserters.fromObject(employee))
  42. .exchange()
  43. .expectStatus().isCreated();
  44. Mockito.verify(repository, times(1)).save(employee);
  45. }
  46. @Test
  47. void testGetEmployeesByName()
  48. {
  49. Employee employee = new Employee();
  50. employee.setId(1);
  51. employee.setName("Test");
  52. employee.setSalary(1000);
  53. List<Employee> list = new ArrayList<Employee>();
  54. list.add(employee);
  55. Flux<Employee> employeeFlux = Flux.fromIterable(list);
  56. Mockito
  57. .when(repository.findByName("Test"))
  58. .thenReturn(employeeFlux);
  59. webClient.get().uri("/name/{name}", "Test")
  60. .header(HttpHeaders.ACCEPT, "application/json")
  61. .exchange()
  62. .expectStatus().isOk()
  63. .expectBodyList(Employee.class);
  64. Mockito.verify(repository, times(1)).findByName("Test");
  65. }
  66. @Test
  67. void testGetEmployeeById()
  68. {
  69. Employee employee = new Employee();
  70. employee.setId(100);
  71. employee.setName("Test");
  72. employee.setSalary(1000);
  73. Mockito
  74. .when(repository.findById(100))
  75. .thenReturn(Mono.just(employee));
  76. webClient.get().uri("/{id}", 100)
  77. .exchange()
  78. .expectStatus().isOk()
  79. .expectBody()
  80. .jsonPath("$.name").isNotEmpty()
  81. .jsonPath("$.id").isEqualTo(100)
  82. .jsonPath("$.name").isEqualTo("Test")
  83. .jsonPath("$.salary").isEqualTo(1000);
  84. Mockito.verify(repository, times(1)).findById(100);
  85. }
  86. @Test
  87. void testDeleteEmployee()
  88. {
  89. Mono<Void> voidReturn = Mono.empty();
  90. Mockito
  91. .when(repository.deleteById(1))
  92. .thenReturn(voidReturn);
  93. webClient.get().uri("/delete/{id}", 1)
  94. .exchange()
  95. .expectStatus().isOk();
  96. }
  97. }
  • 我们正在使用@ExtendWith( SpringExtension.class )支持 Junit 5 中的测试。在 Junit 4 中,我们需要使用@RunWith(SpringRunner.class)
  • 我们使用@Import(EmployeeService.class)为应用程序上下文提供服务依赖,而使用@WebFluxTest时不会自动扫描该上下文。
  • 我们模拟了EmployeeRepository类型为ReactiveMongoRepository的模型。 这将阻止实际的数据库插入和更新。
  • WebTestClient用于命中控制器的特定端点并验证其是否返回正确的状态代码和主体。

2.2. 测试 Spring Boot Webflux 控制器

作为参考,让我们看看上面已经测试过的控制器。

EmployeeController.java

  1. import org.springframework.beans.factory.annotation.Autowired;
  2. import org.springframework.http.HttpStatus;
  3. import org.springframework.http.MediaType;
  4. import org.springframework.http.ResponseEntity;
  5. import org.springframework.web.bind.annotation.DeleteMapping;
  6. import org.springframework.web.bind.annotation.GetMapping;
  7. import org.springframework.web.bind.annotation.PathVariable;
  8. import org.springframework.web.bind.annotation.PostMapping;
  9. import org.springframework.web.bind.annotation.PutMapping;
  10. import org.springframework.web.bind.annotation.RequestBody;
  11. import org.springframework.web.bind.annotation.ResponseStatus;
  12. import org.springframework.web.bind.annotation.RestController;
  13. import com.howtodoinjava.demo.model.Employee;
  14. import com.howtodoinjava.demo.service.EmployeeService;
  15. import reactor.core.publisher.Flux;
  16. import reactor.core.publisher.Mono;
  17. @RestController
  18. public class EmployeeController
  19. {
  20. @Autowired
  21. private EmployeeService employeeService;
  22. @PostMapping(value = { "/create", "/" })
  23. @ResponseStatus(HttpStatus.CREATED)
  24. public void create(@RequestBody Employee e) {
  25. employeeService.create(e);
  26. }
  27. @GetMapping(value = "/{id}")
  28. @ResponseStatus(HttpStatus.OK)
  29. public ResponseEntity<Mono<Employee>> findById(@PathVariable("id") Integer id) {
  30. Mono<Employee> e = employeeService.findById(id);
  31. HttpStatus status = (e != null) ? HttpStatus.OK : HttpStatus.NOT_FOUND;
  32. return new ResponseEntity<>(e, status);
  33. }
  34. @GetMapping(value = "/name/{name}")
  35. @ResponseStatus(HttpStatus.OK)
  36. public Flux<Employee> findByName(@PathVariable("name") String name) {
  37. return employeeService.findByName(name);
  38. }
  39. @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
  40. @ResponseStatus(HttpStatus.OK)
  41. public Flux<Employee> findAll() {
  42. return employeeService.findAll();
  43. }
  44. @PutMapping(value = "/update")
  45. @ResponseStatus(HttpStatus.OK)
  46. public Mono<Employee> update(@RequestBody Employee e) {
  47. return employeeService.update(e);
  48. }
  49. @DeleteMapping(value = "/delete/{id}")
  50. @ResponseStatus(HttpStatus.OK)
  51. public void delete(@PathVariable("id") Integer id) {
  52. employeeService.delete(id).subscribe();
  53. }
  54. }

请使用@WebFluxTestWebTestClient将有关 spring webflux 控制器单元测试的问题交给我。

学习愉快!

下载源码