原文: 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
<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
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
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 控制器单元测试的问题交给我。
学习愉快!