Spring webflux 测试

问题描述

我有一个带有以下服务的 Spring webflux 应用程序。

@Configuration
public class RouterConfiguration {

    @Autowired
    private RegistrationHandler regHandler;

    @Bean
    public RouterFunction<ServerResponse> regRoute() {
        return RouterFunctions.route(POST(REGISTER_PATH).and(accept(APPLICATION_JSON)),regHandler::register);
    }
}

public Mono<ServerResponse> register(ServerRequest request) {
        return request.bodyToMono(Register.class).flatMap(reg -> {
            return buildOrg(reg).flatMap(orgRepository::save).flatMap(org -> 
                         ServerResponse.created(URI.create(USER_RESOURCE_URI + p.getUserId())).build());
        });
    }

我想测试上面的 API 和我的测试示例,

@ExtendWith({ SpringExtension.class,RestDocumentationExtension.class })
@WebFluxTest({ RegistrationHandler.class })
@AutoConfigureWebTestClient(timeout = "100000")
class RegistrationHandlerTest {

    @Autowired
    ApplicationContext context;

    @MockBean
    PasswordEncoder passwordEncoder;

    @MockBean
    private OrgRepository orgRepository;

    @MockBean
    private UserRepository usrRepository;

    @Captor
    private ArgumentCaptor<Organization> orgInputCaptor;

    @Captor
    private ArgumentCaptor<Mono<Organization>> orgMonoInputCaptor;

    @Captor
    private ArgumentCaptor<User> usrInputCaptor;

    private WebTestClient webTestClient;

    @BeforeEach
    void setUp(RestDocumentationContextProvider restDocumentation) {
        webTestClient = WebTestClient.bindToApplicationContext(context).configureClient()
                .filter(documentationConfiguration(restDocumentation)).responseTimeout(Duration.ofMillis(100000))
                .build();
    }

    @Test
    @WithMockUser(username = "admin",roles = { "USER","ADMIN" })
    public void testRegister() {

        final Register register = new Register();
        register.setorgName("testOrg");
        register.setUsername("testUser");
        register.setPassword("testPwd");
        register.setCountry(1);

        final Organization org = new Organization();
        final User usr = new User();

        given(orgRepository.save(orgInputCaptor.capture())).willReturn(Mono.just(org));
        given(usrRepository.save(usrInputCaptor.capture())).willReturn(Mono.just(usr));

        webTestClient.mutateWith(csrf()).post().uri(REGISTER_PATH).contentType(APPLICATION_JSON).bodyValue(register)
                .exchange().expectStatus().is2xxSuccessful().expectBody();

        StepVerifier.create(orgMonoInputCaptor.getValue()).expectNext(org).expectComplete();

        then(usrRepository).should().save(usrInputCaptor.capture());

    }

}

测试用例因 404 NOT_FOUND 错误而失败。

2021-02-02 08:06:37.488  INFO [AdminService,] 28571 --- [    Test worker] s.a.s.h.RegistrationRequestHandlerTest : Starting RegistrationHandlerTest using Java 15.0.2 on 
    2021-02-02 08:06:37.489  INFO [AdminService,] 28571 --- [    Test worker] s.a.s.h.RegistrationHandlerTest : No active profile set,falling back to default profiles: default
    2021-02-02 08:06:38.218  INFO [AdminService,] 28571 --- [    Test worker] c.test.service.admin.AdminApplication   : Starting up Admin Application
    2021-02-02 08:06:38.458  INFO [AdminService,] 28571 --- [    Test worker] ctiveUserDetailsServiceAutoConfiguration : 

    Using generated security password: fcd6963d-cf13-4eb8-a705-d46755493538

    2021-02-02 08:06:38.861  INFO [AdminService,] 28571 --- [    Test worker] s.a.s.h.RegistrationRequestHandlerTest : Started RegistrationHandlerTest in 1.951 seconds (JVM running for 3.765)

RegistrationHandlerTest > testRegister() STANDARD_OUT
    2021-02-02 08:06:39.188 ERROR [AdminService,] 28571 --- [    Test worker] o.s.t.w.reactive.server.ExchangeResult   : Request details for assertion failure:

    > POST http://localhost:8080/register
    > WebTestClient-Request-Id: [1]
    > Content-Type: [application/json]
    > Content-Length: [89]

    {"orgName":"testOrg","username":"testUser","password":"testPwd","country":1,"state":null}

    < 404 NOT_FOUND Not Found
    < Content-Type: [application/json]
    < Content-Length: [135]
    < Cache-Control: [no-cache,no-store,max-age=0,must-revalidate]
    < Pragma: [no-cache]
    < Expires: [0]
    < X-Content-Type-Options: [nosniff]
    < x-frame-options: [DENY]
    < X-XSS-Protection: [1 ; mode=block]
    < Referrer-Policy: [no-referrer]

    {"timestamp":"2021-02-02T16:06:39.150+00:00","path":"/register","status":404,"error":"Not Found","message":null,"requestId":"21d38b1c"}


Gradle Test Executor 13 finished executing tests.

> Task :test-admin:test Failed

RegistrationRequestHandlerTest > testRegister() Failed
    java.lang.AssertionError: Range for response status value 404 NOT_FOUND expected:<SUCCESSFUL> but was:<CLIENT_ERROR>
        at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
        at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
        at org.springframework.test.web.reactive.server.StatusAssertions.lambda$assertSeriesAndReturn$5(StatusAssertions.java:235)
        at org.springframework.test.web.reactive.server.ExchangeResult.assertWithDiagnostics(ExchangeResult.java:227)
        at org.springframework.test.web.reactive.server.StatusAssertions.assertSeriesAndReturn(StatusAssertions.java:233)
        at org.springframework.test.web.reactive.server.StatusAssertions.is2xxSuccessful(StatusAssertions.java:177)
        at com.test.service.admin.spring.handler.RegistrationHandlerTest.testRegister(RegistrationHandlerTest.java:90)

1 test completed,1 Failed

我错过了什么吗?

解决方法

@WebFluxTest 用于测试使用 WebFlux 基于注解的编程模型的应用程序的切片。它包括 @Controller 豆(以及其他)但不包括 RouterFunction 豆。 value@WebFluxTest 属性应用于标识您要测试的特定 @Controller,而不是测试所有 @Controller bean。 @WebFluxTest({ RegistrationHandler.class }) 看起来不对,因为我认为 RegistrationHandler 不是控制器。

您应该改用 @SpringBootTest@Import RouterFunction 实现,如 Spring Boot reference documentation 中所述:

@WebFluxTest 无法检测通过功能性 Web 框架注册的路由。要在上下文中测试 RouterFunction bean,请考虑通过 RouterFunction 或使用 @Import 自己导入您的 @SpringBootTest

,

我错过了 WebFluxTest 注释中的 RouterConfiguration 类,添加相同后它工作正常。

@WebFluxTest({ RegistrationHandler.class,RouterConfiguration.class })