I'm trying to figure out how best to test a simple WebFlux passthrough service starting with the routing part.
(the whole small project is available at https://github.com/ChambreNoire/passthru)
I have a simple functional routing setup like this :
@Configuration
@RequiredArgsConstructor
public class RouterConfig {
private final RouteHandler handler;
@Bean
public RouterFunction<ServerResponse> passthru() {
return route(path("/service/**"), this.handler::doPassthru);
}
// ...
}
which is handled by the following handler :
@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)));
}
// ...
}
using a WebClient defined thus :
@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();
}
}
it just forwards the request to another server via a WebClient and returns the result. (Other routes will do things with the headers etc but I'm dealing with the simplest use case here).
@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);
}
}
The objects sent and received could be anything really but here are the one's used above for the sake of completeness:
@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;
}
The test fails as the returned MyObject isn't the same as myObject but I don't understand why. Plus I'd like to capture the request but I'm not sure how to go about this.
UPDATE!
Ok so as pointed out by @Toerktumlare I shouldn't be testing for equality, however, I'm still unsure as to what the best testing approach for this method is. So far I'm using a MockWebServer
and a WebTestClient
thus :
@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));
});
}
}
It works fine however I'm not sure whether this is sufficient for testing this endpoint...
Cheers