Search code examples
junit5micronaut

Micronaut Exception Handler in JUnit 5


I am seeing different behaviour when running micronaut, compared with when running a JUnit test.

I have created a test controller:

package com.testing;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Controller("/test")
public class TestController {

    private static final Logger log = LoggerFactory.getLogger(TestController.class);

    @Get("/throw")
    public Object getThrow() {
        log.info("Testing throw");
        throw new TestException("Testing throw");
    }
}

My exception class is:

package com.testing;

public class TestException extends RuntimeException {
    public TestException(String message) {
        super(message);
    }
}

I have created a custom record to represent an error payload:

package com.testing;

public record CustomError (int code, String message, String description) { }

And I have an ExceptionHandler implementation of:

package com.testing;

import io.micronaut.context.annotation.Requires;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Produces;
import io.micronaut.http.server.exceptions.ExceptionHandler;
import jakarta.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Produces
@Singleton
@Requires(classes = {TestException.class, ExceptionHandler.class})
public class TestExceptionHandler implements ExceptionHandler<TestException, HttpResponse<CustomError>> {

    private static final Logger log = LoggerFactory.getLogger(TestExceptionHandler.class);

    @Override
    public HttpResponse<CustomError> handle(HttpRequest request, TestException exception) {
        log.info("In handle method");
        var error = new CustomError(
                HttpStatus.BAD_REQUEST.getCode(),
                HttpStatus.BAD_REQUEST.name(),
                "Bad Request found");
        return HttpResponse.badRequest().body(error);
    }
}

When I run my micronaut application, the ExceptionHandler is correctly called, and I see a response of:

HTTP/1.1 400 Bad Request
date: <the date>
Content-Type: application/json
content-length: 70
connection: keep-alive

{
  "code": 400,
  "message": "BAD_REQUEST",
  "description": "Bad Request found"
}

However, when I try and test this in a JUnit test, the client seems to throw an exception:

package com.testing;

import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;

@MicronautTest
public class TestControllerTest {

    private static final Logger log = LoggerFactory.getLogger(TestControllerTest.class);

    @Inject
    @Client("/test")
    HttpClient client;

    @Test
    public void testThrow() {
        log.info("Calling throw endpoint");
        assertDoesNotThrow(() -> {
            var response = client.toBlocking().exchange("/throw");
            log.info("Response: {}, {}", response, response.body());
            assertEquals(HttpStatus.BAD_REQUEST, response.getStatus());
        });
    }
}

The console / stacktrace starts with:

16:14:51.684 [Test worker] INFO  i.m.context.env.DefaultEnvironment - Established active environments: [test]
16:14:52.734 [Test worker] INFO  com.testing.TestControllerTest - Calling throw endpoint
16:14:52.873 [default-nioEventLoopGroup-1-3] INFO  com.testing.TestController - Testing throw
16:14:52.891 [default-nioEventLoopGroup-1-3] INFO  com.testing.TestExceptionHandler - In handle method

Unexpected exception thrown: io.micronaut.http.client.exceptions.HttpClientResponseException: BAD_REQUEST
org.opentest4j.AssertionFailedError: Unexpected exception thrown: io.micronaut.http.client.exceptions.HttpClientResponseException: BAD_REQUEST
    at app//org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:152)

Is there something additional I need to do to ensure my client can receive the response correctly?


Solution

  • Ah. My mistake. By default the HttpClient says it throws an exception for an error status:

    I can override this behaviour using the following:

    # application.yml
    micronaut:
      http:
        client:
          exception-on-error-status: false
    
    @Test
    public void testThrow() {
        log.info("Calling throw endpoint");
        var response = client.toBlocking().exchange("/throw", String.class, String.class);
        assertEquals(HttpStatus.BAD_REQUEST, response.getStatus());
        log.info("Response: {}", response.body());
        assertEquals("{\"code\":400,\"message\":\"BAD_REQUEST\",\"description\":\"Bad Request found\"}", response.body());
    }
    

    Or I can allow an exception to be thrown, and use code similar to the following instead:

    @Test
    public void testThrow() {
        log.info("Calling throw endpoint");
        var error = assertThrows(HttpClientResponseException.class, () -> {
            client.toBlocking().exchange("/throw");
        });
        assertEquals(HttpStatus.BAD_REQUEST, error.getStatus());
        assertEquals("BAD_REQUEST", error.getMessage());
    }