Reputation: 445
I'am writing a complex application as an experiment with Spring 5 Webflux. I'm planning to use lot's of techniques in this application. I'm familiar with the "old style" @RestController, but now, I'm writing functional endpoints. E.g. the backend of a company registration service.I was after something like the @ControllerAdvice in the "old world". But I wasn't really able to find anything similar as a reactive equivalent. (Or anything what worked out for me.)
I have a very basic setup. A routing function, a reactive Cassandra repository, a handler and a test class. The repository operation can throw IllegalArgumentException, and I'd like to handle it by returning HTTP Status BadRequest to client. Just as an example of I'm capable to do it. :-) The exception is taken care by the handler class. Here is my code.
RouterConfig
@Slf4j
@Configuration
@EnableWebFlux
public class RouterConfig {
@Bean
public RouterFunction<ServerResponse> route(@Autowired CompanyHandler handler) {
return RouterFunctions.route()
.nest(path("/company"), bc -> bc
.GET("/{id}", handler::handleGetCompanyDataRequest)
.before(request -> {
log.info("Request={} has been received.", request.toString());
return request;
})
.after((request, response) -> {
log.info("Response={} has been sent.", response.toString());
return response;
}))
.build();
}
}
CompanyHandler
@Slf4j
@Component
public class CompanyHandler {
@Autowired
private ReactiveCompanyRepository repository;
// Handle get single company data request
public Mono<ServerResponse> handleGetCompanyDataRequest(ServerRequest request) {
//Some validation ges here
return repository.findById(Mono.just(uuid))
.flatMap(this::ok)
.onErrorResume(IllegalArgumentException.class, e -> ServerResponse.badRequest().build())
.switchIfEmpty(ServerResponse.notFound().build());
}
private Mono<ServerResponse> ok (Company c) {
return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromPublisher(Mono.just(c), Company.class));
}
}
ReactiveCompanyRepository
@Component
public interface ReactiveCompanyRepository extends ReactiveCassandraRepository<Company, UUID>{
Mono<Company> findByName(String name);
Mono<Company> findByEmail(String email);
}
My problem is that .onErrorResume(IllegalArgumentException.class, e -> ServerResponse.badRequest().build()) is never called and in the test case:
@SuppressWarnings("unchecked")
@Test
public void testGetCompanyExceptionDuringFind() {
Mockito.when(repository.findById(Mockito.any(Mono.class))).thenThrow(new IllegalArgumentException("Hahaha"));
WebTestClient.bindToRouterFunction(routerConfig.route(companyHandler))
.build()
.get().uri("/company/2b851f10-356e-11e9-a847-0f89e1aa5554")
.accept(MediaType.APPLICATION_JSON_UTF8)
.exchange()
.expectStatus().isBadRequest()
.returnResult(Company.class)
.getResponseBody();
}
I always get HttpStatus 500 instead of 400. So it fails.Any help would be appreciated!
Upvotes: 4
Views: 4360
Reputation: 59076
Your test does not represent how a reactive API is supposed to behave. If such a repository throws an exception directly, I'd consider that a bug a report that to the maintainers.
When a reactive API returns a reactive type like Mono
or Flux
, it is expected that all errors are not thrown directly but actually sent as messages in the reactive pipeline.
In this case, your test case should probably be more like:
Mockito.when(repository.findById(Mockito.any(Mono.class)))
.thenReturn(Mono.error(new IllegalArgumentException("Hahaha")));
With that, the
onError...
operators in Reactor will handle those error messages.
Upvotes: 1