bkk
bkk

Reputation: 445

Spring 5 reactive exception handling

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

Answers (1)

Brian Clozel
Brian Clozel

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

Related Questions