Mockito重复调用when()抛出异常的问题排查与解决
问题描述
我有一个可发起API调用的Api类,接收Long和Integer类型参数。Controller类中调用该API并处理潜在错误的核心代码如下:
public String save() { try { String rc = api.doApiCall(l, i); if(rc.equals("200")) return "nextpage.html"; } catch (RuntimeException ignored) { } return "error.html"; }
使用Mockito模拟Api类编写的Controller测试用例如下:
@BeforeEach void setUp() { controller = new Controller(); api = mock(Api.class); controller.api = api; } @Test void testApiCall() { when(api.doApiCall(any(Long.class), any(Integer.class))) .thenReturn("200"); assertEquals("nextpage.html", controller.save()); when(api.doApiCall(any(Long.class), any(Integer.class))) .thenThrow(new RuntimeException("400")); assertEquals("error.html", controller.save()); when(api.doApiCall(any(Long.class), any(Integer.class))) .thenThrow(new RuntimeException("401")); assertEquals("error.html", controller.save()); }
执行测试时,第三个when()调用会抛出第二个when()中指定的异常,导致测试失败。尝试将第三个when()中的any(Integer.class)替换为any()后测试正常,但新增如下测试用例时仍出现相同问题:
when(api.doApiCall(any(Long.class), any())) .thenThrow(new RuntimeException("402")); assertEquals("error.html", controller.save());
问题原因
问题出在Mockito的when()语法执行逻辑上:使用when(mock.method(...))时,会先执行一次mock对象的方法调用,再将后续的thenReturn/thenThrow关联到这个调用上。
当你在第二个存根中设置了thenThrow(new RuntimeException("400"))后,再次调用when(api.doApiCall(any(Long.class), any(Integer.class)))时,这个方法调用会触发之前的存根逻辑,直接抛出RuntimeException("400"),导致无法完成新的存根设置,进而测试失败。
解决方法
方法一:改用doThrow/doReturn系列语法
这类语法是先指定行为,再绑定到目标方法,不会提前执行mock的方法调用,避免触发之前的存根异常。修改后的测试用例如下:
@Test void testApiCall() { // 存根成功返回场景 doReturn("200").when(api).doApiCall(any(Long.class), any(Integer.class)); assertEquals("nextpage.html", controller.save()); // 存根抛出400异常场景 doThrow(new RuntimeException("400")).when(api).doApiCall(any(Long.class), any(Integer.class)); assertEquals("error.html", controller.save()); // 存根抛出401异常场景 doThrow(new RuntimeException("401")).when(api).doApiCall(any(Long.class), any(Integer.class)); assertEquals("error.html", controller.save()); // 存根抛出402异常场景 doThrow(new RuntimeException("402")).when(api).doApiCall(any(Long.class), any()); assertEquals("error.html", controller.save()); }
方法二:拆分测试场景为独立方法
将不同的测试场景拆分为独立的测试方法,每个测试方法单独存根,避免在同一个测试方法中多次修改存根,同时让测试逻辑更清晰:
@Test void testApiCallSuccess() { doReturn("200").when(api).doApiCall(any(Long.class), any(Integer.class)); assertEquals("nextpage.html", controller.save()); } @Test void testApiCallThrow400() { doThrow(new RuntimeException("400")).when(api).doApiCall(any(Long.class), any(Integer.class)); assertEquals("error.html", controller.save()); } @Test void testApiCallThrow401() { doThrow(new RuntimeException("401")).when(api).doApiCall(any(Long.class), any(Integer.class)); assertEquals("error.html", controller.save()); } @Test void testApiCallThrow402() { doThrow(new RuntimeException("402")).when(api).doApiCall(any(Long.class), any()); assertEquals("error.html", controller.save()); }
内容的提问来源于stack exchange,提问作者Buecherregale
相关产品推荐
相关产品推荐

