多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# 使用`@WebFluxTest`和`WebTestClient`进行 Spring Boot Webflux 测试 > 原文: [https://howtodoinjava.com/spring-webflux/webfluxtest-with-webtestclient/](https://howtodoinjava.com/spring-webflux/webfluxtest-with-webtestclient/) 学习使用`@WebFluxTest`注解和`WebTestClient`来对 [spring boot webflux](https://howtodoinjava.com/spring-webflux/spring-webflux-tutorial/) 控制器进行单元测试,该测试用于通过 [**Junit 5**](https://howtodoinjava.com/spring-5-tutorial/) 测试 webflux 端点。 ## 1\. 使用`WebTestClient`的`@WebFluxTest` #### 1.1. Maven 依赖 添加[`reactor-test`](https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22io.projectreactor%22%20AND%20a%3A%22reactor-test%22)依赖。 `pom.xml` ```java <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> ``` #### 1.2. `@WebFluxTest`注解 它需要完全自动配置,而仅应用与 WebFlux 测试相关的配置(例如`@Controller`,`@ControllerAdvice`,`@JsonComponent`,`Converter`和`WebFluxConfigurer` bean,但没有`@Component`,`@Service`或`@Repository` bean)。 默认情况下,用`@WebFluxTest`注解的测试还将自动配置`WebTestClient`。 通常,`@WebFluxTest`与`@MockBean`或`@Import`结合使用以创建`@Controller` bean 所需的任何协作者。 > 要编写需要完整应用程序上下文的集成测试,请考虑将`@SpringBootTest`与`@AutoConfigureWebTestClient`结合使用。 #### 1.3. `WebTestClient` 它是用于测试 Web 服务器的非阻塞式响应客户端,该客户端内部使用响应`WebClient`来执行请求,并提供流利的 API 来验证响应。 它可以通过 HTTP 连接到任何服务器,或使用模拟请求和响应对象直接绑定到 WebFlux 应用程序,而无需 HTTP 服务器。 `WebTestClient`与`MockMvc`相似。 这些测试 Web 客户端之间的唯一区别是`WebTestClient`旨在测试 WebFlux 端点。 ## 2\. 测试 webflux 控制器 #### 2.1. 用于 Webflux 控制器的 Junit 5 测试 在给定的示例中,我们正在测试`EmployeeController`类,该类包含用于 CRUD 操作的指令方法。 `EmployeeControllerTest.java` ```java import static org.mockito.Mockito.times; import java.util.ArrayList; import java.util.List; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.reactive.WebFluxTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Import; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.web.reactive.function.BodyInserters; import com.howtodoinjava.demo.controller.EmployeeController; import com.howtodoinjava.demo.dao.EmployeeRepository; import com.howtodoinjava.demo.model.Employee; import com.howtodoinjava.demo.service.EmployeeService; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @ExtendWith(SpringExtension.class) @WebFluxTest(controllers = EmployeeController.class) @Import(EmployeeService.class) public class EmployeeControllerTest { @MockBean EmployeeRepository repository; @Autowired private WebTestClient webClient; @Test void testCreateEmployee() { Employee employee = new Employee(); employee.setId(1); employee.setName("Test"); employee.setSalary(1000); Mockito.when(repository.save(employee)).thenReturn(Mono.just(employee)); webClient.post() .uri("/create") .contentType(MediaType.APPLICATION_JSON) .body(BodyInserters.fromObject(employee)) .exchange() .expectStatus().isCreated(); Mockito.verify(repository, times(1)).save(employee); } @Test void testGetEmployeesByName() { Employee employee = new Employee(); employee.setId(1); employee.setName("Test"); employee.setSalary(1000); List<Employee> list = new ArrayList<Employee>(); list.add(employee); Flux<Employee> employeeFlux = Flux.fromIterable(list); Mockito .when(repository.findByName("Test")) .thenReturn(employeeFlux); webClient.get().uri("/name/{name}", "Test") .header(HttpHeaders.ACCEPT, "application/json") .exchange() .expectStatus().isOk() .expectBodyList(Employee.class); Mockito.verify(repository, times(1)).findByName("Test"); } @Test void testGetEmployeeById() { Employee employee = new Employee(); employee.setId(100); employee.setName("Test"); employee.setSalary(1000); Mockito .when(repository.findById(100)) .thenReturn(Mono.just(employee)); webClient.get().uri("/{id}", 100) .exchange() .expectStatus().isOk() .expectBody() .jsonPath("$.name").isNotEmpty() .jsonPath("$.id").isEqualTo(100) .jsonPath("$.name").isEqualTo("Test") .jsonPath("$.salary").isEqualTo(1000); Mockito.verify(repository, times(1)).findById(100); } @Test void testDeleteEmployee() { Mono<Void> voidReturn = Mono.empty(); Mockito .when(repository.deleteById(1)) .thenReturn(voidReturn); webClient.get().uri("/delete/{id}", 1) .exchange() .expectStatus().isOk(); } } ``` * 我们正在使用`@ExtendWith( SpringExtension.class )`支持 Junit 5 中的测试。在 Junit 4 中,我们需要使用`@RunWith(SpringRunner.class)`。 * 我们使用`@Import(EmployeeService.class)`为应用程序上下文提供服务依赖,而使用`@WebFluxTest`时不会自动扫描该上下文。 * 我们模拟了`EmployeeRepository`类型为`ReactiveMongoRepository`的模型。 这将阻止实际的数据库插入和更新。 * `WebTestClient`用于命中控制器的特定端点并验证其是否返回正确的状态代码和主体。 #### 2.2. 测试 Spring Boot Webflux 控制器 作为参考,让我们看看上面已经测试过的控制器。 `EmployeeController.java` ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import com.howtodoinjava.demo.model.Employee; import com.howtodoinjava.demo.service.EmployeeService; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @RestController public class EmployeeController { @Autowired private EmployeeService employeeService; @PostMapping(value = { "/create", "/" }) @ResponseStatus(HttpStatus.CREATED) public void create(@RequestBody Employee e) { employeeService.create(e); } @GetMapping(value = "/{id}") @ResponseStatus(HttpStatus.OK) public ResponseEntity<Mono<Employee>> findById(@PathVariable("id") Integer id) { Mono<Employee> e = employeeService.findById(id); HttpStatus status = (e != null) ? HttpStatus.OK : HttpStatus.NOT_FOUND; return new ResponseEntity<>(e, status); } @GetMapping(value = "/name/{name}") @ResponseStatus(HttpStatus.OK) public Flux<Employee> findByName(@PathVariable("name") String name) { return employeeService.findByName(name); } @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE) @ResponseStatus(HttpStatus.OK) public Flux<Employee> findAll() { return employeeService.findAll(); } @PutMapping(value = "/update") @ResponseStatus(HttpStatus.OK) public Mono<Employee> update(@RequestBody Employee e) { return employeeService.update(e); } @DeleteMapping(value = "/delete/{id}") @ResponseStatus(HttpStatus.OK) public void delete(@PathVariable("id") Integer id) { employeeService.delete(id).subscribe(); } } ``` 请使用`@WebFluxTest`和`WebTestClient`将有关 **spring webflux 控制器单元测试**的问题交给我。 学习愉快! [下载源码](https://github.com/lokeshgupta1981/SpringExamples/blob/master/spring-webflux-demo/src/test/java/com/howtodoinjava/demo/EmployeeControllerTest.java)