Micronaut自定义ExceptionHandler在JUnit 5测试中与应用运行时表现不一致的问题排查
这个问题其实是Micronaut HttpClient在测试场景下的默认行为差异导致的——你直接运行应用时,外部客户端(比如浏览器、Postman)会直接接收ExceptionHandler生成的400响应;但测试里用的Micronaut HttpClient,默认会对4xx/5xx这类错误状态码自动抛出HttpClientResponseException,而不是返回响应对象。
问题原因拆解
你的自定义ExceptionHandler其实已经正常工作了(日志里也打印了In handle method),但测试代码里的assertDoesNotThrow阻止了异常的正常抛出,反而触发了JUnit的断言失败。本质是你误解了测试用HttpClient的行为逻辑——它不会像外部客户端那样直接返回错误响应,而是将错误响应包装成异常抛出。
解决方案(三种可选)
方案1:捕获异常并验证异常中的响应内容
这是最直接的调整方式,去掉assertDoesNotThrow,主动捕获HttpClientResponseException,然后从异常里提取响应信息做断言:
@Test public void testThrow() { log.info("Calling throw endpoint"); // 捕获预期的异常 HttpClientResponseException exception = assertThrows(HttpClientResponseException.class, () -> { client.toBlocking().exchange("/throw"); }); // 验证状态码 assertEquals(HttpStatus.BAD_REQUEST, exception.getStatus()); // 验证自定义错误体 CustomError error = exception.getResponse().body(CustomError.class); assertEquals(400, error.code()); assertEquals("BAD_REQUEST", error.message()); assertEquals("Bad Request found", error.description()); }
方案2:使用支持错误响应的客户端调用方法
可以使用exchange的重载方法,指定允许接收的错误状态码,或者用retrieve方法直接获取错误响应体:
@Test public void testThrow() { log.info("Calling throw endpoint"); // 指定接收BAD_REQUEST状态码的响应 HttpResponse<CustomError> response = client.toBlocking() .exchange("/throw", CustomError.class, Argument.of(HttpStatus.class, HttpStatus.BAD_REQUEST)); assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); CustomError error = response.body(); // 其他断言逻辑 }
方案3:修改客户端配置,禁用自动抛出异常
如果希望测试用HttpClient和外部客户端行为一致,可以自定义客户端配置,关闭错误状态码自动抛异常的逻辑:
@MicronautTest public class TestControllerTest { private static final Logger log = LoggerFactory.getLogger(TestControllerTest.class); @Inject @Client(value = "/test", configuration = TestClientConfig.class) HttpClient client; // 自定义客户端配置类 @Singleton public static class TestClientConfig implements HttpClientConfiguration { @Override public Class<? extends Throwable> getDefaultErrorType() { return null; // 禁用错误状态码自动抛异常 } } @Test public void testThrow() { log.info("Calling throw endpoint"); // 现在不会抛出异常,可以直接获取响应 HttpResponse<CustomError> response = client.toBlocking().exchange("/throw", CustomError.class); log.info("Response: {}, {}", response, response.body()); assertEquals(HttpStatus.BAD_REQUEST, response.getStatus()); // 验证响应体 CustomError error = response.body(); assertEquals(400, error.code()); } }
总结
核心是要区分应用端的异常处理逻辑和测试客户端的响应处理逻辑:你的ExceptionHandler已经正确生成了错误响应,只是测试用的HttpClient默认会把错误响应转换成异常抛出,只需要调整测试代码或客户端配置即可匹配预期行为。
备注:内容来源于stack exchange,提问作者toolkit

