Mockito中doNothing触发UnfinishedStubbingException问题求助
这个问题我之前也碰到过,UnfinishedStubbingException的根源是你在stubbing(存根)方法的过程中,不小心触发了另一个真实方法的调用,或者嵌套了未完成的stub操作,打乱了Mockito的内部上下文。咱们一步步来拆解和解决:
先排查明显的笔误
看你的代码里,你stub的是spyAnotherService.getUrl(ID),但在deleteSomething里传入的是spyAnotherService.getRepApiServiceUrl(ID)——这两个方法名不一样!如果这是手滑写错了,那首先得把stub的方法名改成和实际调用的一致,否则你的stub根本不会生效。
核心问题:不要在Stubbing参数中直接调用Spy方法
你当前的代码里,在设置myService.deleteSomething的存根时,直接把spyAnotherService.getRepApiServiceUrl(ID)作为参数传进去了:
Mockito.doNothing().when(myService).deleteSomething(name, spyAnotherService.getRepApiServiceUrl(ID));
这会导致在stubbing myService的过程中,真实调用了spyAnotherService的getRepApiServiceUrl方法(因为Spy默认会执行真实方法,除非你用doReturn提前存根),而这个真实调用可能触发了未预期的逻辑,或者和Mockito的存根流程冲突,从而抛出异常。
正确的两种解决方式
方式1:提前获取Stub后的结果,再传入参数
先存根spy的方法,然后把存根后的结果存到变量里,再用这个变量作为参数设置myService的存根:
// 1. 先存根spy的目标方法(注意方法名要和实际调用的一致) // 用doReturn可以避免调用真实方法,更安全 Mockito.doReturn(URL).when(spyAnotherService).getRepApiServiceUrl(ID); // 2. 获取存根后的URL(这里不会调用真实方法) String mockedApiUrl = spyAnotherService.getRepApiServiceUrl(ID); // 3. 设置myService的存根,用提前获取的变量作为参数 Mockito.doNothing().when(myService).deleteSomething(name, mockedApiUrl);
方式2:使用参数匹配器
如果你不需要实际获取那个URL,也可以用Mockito的参数匹配器来匹配参数,避免直接调用spy方法:
// 先存根spy的方法 Mockito.doReturn(URL).when(spyAnotherService).getRepApiServiceUrl(ID); // 使用eq匹配器来指定参数值,这里直接用你要模拟的URL即可 Mockito.doNothing().when(myService).deleteSomething(eq(name), eq(URL));
如果不关心第二个参数的具体值,也可以用anyString()代替eq(URL)。
额外提示:Spy的正确使用姿势
当你使用Spy时,尽量用doReturn(...).when(spy).method(...)来存根,而不是when(spy.method(...)).thenReturn(...)——因为后者会先调用一次真实的方法,如果你真实方法有副作用(比如访问数据库、抛出异常),就会出问题。前者则完全跳过真实方法,直接返回模拟值。
内容的提问来源于stack exchange,提问作者Lisa

