Search code examples
micronautmicronaut-test

How Do I Unit Test Micronaut Error Handlers?


There are two methods in my Controller, the POST controller:

    @Post
    HttpResponse<Publisher<ControllerResponseModel>> post(@Body String designJson) throws JsonProcessingException {
        Publisher<ControllerResponseModel> response = designService.save(designJson);
        return HttpResponse.created(response);
    }

... and the Error handler if the JSON is malformed:

    @Error(exception = JsonProcessingException.class)
    HttpResponse<Publisher<ErrorModel>> jsonProcessingException(HttpRequest request, JsonProcessingException exception) {
        return HttpResponse.badRequest(Mono.just(new ErrorModel(exception.getMessage())));
    }

When I run the application, everything works as intended. A response of ErrorModel is shown for malformed JSON; a response of the proper type for properly formed JSON.

But I can't seem to unit test the error handlers in the Controller.

My hope was something like this would work. I mock out the service and have it throw the proper exception. But when it's run, the thrown exception fails the test and the response object isn't populated or asserted against.

    @Test
    void post_malformedJson() throws IOException {
        final String jsonData = "var: 37";
        final JsonParser parser = JsonFactory.builder().build().createParser(jsonData);
        doThrow(new JsonParseException(parser, "")).when(designService).save(anyString());

        HttpResponse<Publisher<ControllerResponseModel>> response = instance.post(jsonData);
        assertThat(response.getBody().isPresent()).isTrue();

        StepVerifier.create(response.getBody().get())
                .expectNext(new ErrorModel("SOMETHING"))
                .expectComplete()
                .verify();
    }

Any tips (even if it's a completely different structure than above) on how to unit test Micronaut Error handlers?


Solution

  • Looking at the test, I would guess you are instantiating the resource class yourself. As you can see, the controlled doesn't actually call the handler anywhere. It is used by Micronaut to create a proxy that catches the specified exception and handles it as defined in the handler.

    To make this work you need more than just the controller instance. That's what the @MicronautTest annotation is for. It starts up your application, including the error handlers.

    Test itself is similar to what you have. Mock design service to skip business logic and simulate the error. The main difference is that you need to invoke the operation through your endpoint since it's running. Here I am using rest-assured to send the POST request.

    @MicronautTest
    class DemoTest {
    
        @Inject
        DesignService designService;
    
        @MockBean(DesignService.class)
        DesignService designService() {
            return Mockito.mock(DesignService.class);
        }
    
        @Test
        public void testHelloEndpoint(RequestSpecification spec) throws JsonProcessingException {
            final String expectedMessage = "my message";
            final String malformedBody = "var: 37";
            Mockito.when(this.designService.save(ArgumentMatchers.anyString()))
                .thenThrow(new MyParsingException(expectedMessage));
    
            String error = spec
                .given().body(malformedBody).contentType(ContentType.TEXT)
                .when().post("/hello")
                .then()
                .statusCode(400)
                // Can also use .as(ErrorModel.class) and assert on that
                .extract().body().asString();
    
            assertTrue(error.contains(expectedMessage));
        }
    
        class MyParsingException extends JsonProcessingException {
            public MyParsingException(String msg) {
                super(msg);
            }
        }
    
    }