我正在尝试找出从路由部分开始测试简单 WebFlux 直通服务的最佳方法。
(整个 small 项目可在 https://github.com/ChambreNoire/passthru 获得)
我有一个简单的功能路由设置,如下所示:
@Configuration
@RequiredArgsConstructor
public class RouterConfig {
private final RouteHandler handler;
@Bean
public RouterFunction<ServerResponse> passthru() {
return route(path("/service/**"), this.handler::doPassthru);
}
// ...
}
由以下处理程序处理:
@Component
@RequiredArgsConstructor
public class RouteHandler {
private final WebClient webClient;
public Mono<ServerResponse> passthru(ServerRequest request) {
return webClient.method(request.method())
.uri(request.path())
.headers(httpHeaders -> request.headers().asHttpHeaders().forEach(httpHeaders::put))
.body(request.bodyToMono(PooledDataBuffer.class), PooledDataBuffer.class)
.exchangeToMono(response -> response.bodyToMono(PooledDataBuffer.class)
.flatMap(buffer -> ServerResponse
.status(response.statusCode())
.headers(headers -> headers.addAll(response.headers().asHttpHeaders()))
.bodyValue(buffer)));
}
// ...
}
使用这样定义的 WebClient :
@Configuration
public class WebClientConfig {
@Bean
public WebClient webClient(@Value("${target.url}") String targetUrl) {
return WebClient.builder()
.baseUrl(targetUrl)
.clientConnector(new ReactorClientHttpConnector(HttpClient.create()
.followRedirect(true)))
.build();
}
}
它只是通过 WebClient 将请求转发到另一台服务器并返回结果。 (其他路由会处理标题等,但我在这里处理最简单的用例)。
@SpringBootTest(
classes = MyApplication.class,
webEnvironment = RANDOM_PORT
)
@RunWith(SpringRunner.class)
public class RoutingTest {
@Autowired
private RouterConfig config;
@MockBean
private RouteHandler handler;
@Test
public void passthru() {
WebTestClient testClient = WebTestClient
.bindToRouterFunction(config.passthru())
.build();
final AuthRequest authRequest = AuthRequest.builder()
.secretKey("my.secret.key")
.build();
final AccessTokens accessTokens = AccessTokens.builder()
.accountId("accountId")
.accessToken("accessToken")
.build();
final Mono<ServerResponse> response = ServerResponse.ok()
.body(Mono.just(accessTokens), AccessTokens.class);
when(handler.passthru(any(ServerRequest.class))).thenReturn(response);
testClient
.post()
.uri("/service/auth")
.body(Mono.just(authRequest), AuthRequest.class)
.exchange()
.expectStatus().isOk()
.expectBody(AuthRequest.class)
.isEqualTo(authRequest);
}
}
发送和接收的对象可以是任何东西,但为了完整起见,以下是上面使用的对象:
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class AccessTokens {
@JsonProperty("account")
private String accountId;
private String accessToken;
}
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class AuthRequest {
private String secretKey;
}
测试失败,因为返回的 MyObject 与 myObject 不同,但我不明白为什么。另外,我想捕获请求,但我不知道如何处理。
更新!
好的,正如@Toerktumlare 所指出的,我不应该测试相等性,但是,我仍然不确定这种方法的最佳测试方法是什么。到目前为止,我正在使用 MockWebServer
和 WebTestClient
:
@SpringBootTest(
classes = MyApplication.class,
webEnvironment = RANDOM_PORT
)
public class HandlerTest {
@Autowired
private RouterConfig config;
static MockWebServer mockBackEnd;
private static AuthRequest authRequest;
private static AccessTokens accessTokens;
@SuppressWarnings("unused")
@DynamicPropertySource
static void properties(DynamicPropertyRegistry registry) {
registry.add("gateway.url", () -> "http://localhost:" + mockBackEnd.getPort());
}
@BeforeAll
static void setUp() throws IOException {
mockBackEnd = new MockWebServer();
mockBackEnd.start();
authRequest = AuthRequest.builder()
.secretKey("secretKey")
.build();
accessTokens = AccessTokens.builder()
.accountId("accountId")
.accessToken("accessToken")
.renewToken("renewToken")
.type("Bearer")
.build();
}
@AfterAll
static void tearDown() throws IOException {
mockBackEnd.shutdown();
}
@Test
public void testPassthru() throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
mockBackEnd.enqueue(new MockResponse()
.setResponseCode(HttpStatus.OK.value())
.setBody(objectMapper.writeValueAsString(accessTokens))
.addHeader("Content-Type", "application/json"));
WebTestClient testClient = WebTestClient
.bindToRouterFunction(config.passthru())
.build();
testClient.post()
.uri("/service/auth")
.body(Mono.just(authRequest), AuthRequest.class)
.exchange()
.expectStatus().isOk()
.expectBody(AccessTokens.class)
.value(tokens -> {
assertThat(tokens)
.returns("accountId", from(AccessTokens::getAccountId))
.returns("accessToken", from(AccessTokens::getAccessToken))
.returns("renewToken", from(AccessTokens::getRenewToken))
.returns("Bearer", from(AccessTokens::getType));
});
}
}
它工作正常,但我不确定这是否足以测试此端点...
干杯