原文: https://howtodoinjava.com/spring-webflux/webfluxtest-with-webtestclient/
学习使用@WebFluxTest注解和WebTestClient来对 spring boot webflux 控制器进行单元测试,该测试用于通过 Junit 5 测试 webflux 端点。
添加reactor-test依赖。
pom.xml
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>它需要完全自动配置,而仅应用与 WebFlux 测试相关的配置(例如@Controller,@ControllerAdvice,@JsonComponent,Converter和WebFluxConfigurer bean,但没有@Component,@Service或@Repository bean)。
默认情况下,用@WebFluxTest注解的测试还将自动配置WebTestClient。
通常,@WebFluxTest与@MockBean或@Import结合使用以创建@Controller bean 所需的任何协作者。
要编写需要完整应用程序上下文的集成测试,请考虑将
@SpringBootTest与@AutoConfigureWebTestClient结合使用。
它是用于测试 Web 服务器的非阻塞式响应客户端,该客户端内部使用响应WebClient来执行请求,并提供流利的 API 来验证响应。
它可以通过 HTTP 连接到任何服务器,或使用模拟请求和响应对象直接绑定到 WebFlux 应用程序,而无需 HTTP 服务器。
WebTestClient与MockMvc相似。 这些测试 Web 客户端之间的唯一区别是WebTestClient旨在测试 WebFlux 端点。
在给定的示例中,我们正在测试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用于命中控制器的特定端点并验证其是否返回正确的状态代码和主体。
作为参考,让我们看看上面已经测试过的控制器。
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 控制器单元测试的问题交给我。
学习愉快!