启用mock-maker-inline后含final方法trait的测试用例失败问题
mock-maker-inline启用后带final方法的trait测试报错排查
根因
- 未启用
mock-maker-inline时,Mockito默认基于子类生成代理实现,无法重写final类、final方法:示例代码中Shapetrait的printArea()是final方法,mock生成的Rectangle代理类不会拦截这个方法,调用时直接走trait内的真实逻辑,会触发内部对getArea()的调用,因此预先桩化的getArea()会被实际执行,不会触发未使用桩的异常。 - 启用
mock-maker-inline后,Mockito通过类加载期字节码插桩实现mock能力,可以拦截所有方法(包括final方法),此时mock实例的所有方法默认返回对应类型的默认值(Unit返回()、引用类型返回null、数值类型返回0/0.0等),不会执行任何真实逻辑:测试中调用shape.printArea()时,直接命中Mockito生成的默认桩返回(),完全不会执行printArea()内部调用getArea()的代码,定义的rectangle.getArea() shouldReturn 40.0桩从头到尾没被调用。 - 测试中混入的
IdiomaticMockitoFixture默认开启Mockito严格桩检测,一旦存在定义了但没被调用的桩,就会直接抛出UnnecessaryStubbingException。
可行修复方案
方案1(推荐,符合测试意图)
对需要执行真实逻辑的final方法,手动指定调用真实实现,在Setup块补充对应配置即可:
trait Setup { val rectangle = mock[Rectangle] // 让final方法printArea走真实业务逻辑,才会内部触发getArea调用 rectangle.printArea() shouldCallRealMethod val areaCalculator = new AreaCalculator(rectangle) }
修改后执行测试,printArea()会运行trait中的真实代码,触发getArea()调用,预先定义的桩会被正常使用,异常消失。
方案2(不推荐,仅临时适配用)
如果不需要执行printArea()的真实逻辑,要么删除没有被使用的getArea()桩定义,要么将对应桩设置为宽松模式,跳过严格检测:
// 导入lenient方法 import org.mockito.Mockito.lenient "should invoke area of the appropriate shape" in new Setup { // 给桩加宽松标记,不检测是否被调用 lenient() when rectangle.getArea() returns 40.0 areaCalculator.printArea() mustBe true }
不建议全局开启宽松桩模式,会降低测试严谨性,容易遗留无效测试代码。
注意事项
启用mock-maker-inline后,之前依赖"final方法不会被mock、默认走真实逻辑"的测试行为全部会失效,所有需要执行真实逻辑的方法(哪怕是final修饰、来自父类/父trait),都需要手动指定shouldCallRealMethod,避免被默认桩替换导致业务逻辑不执行。
内容的提问来源于stack exchange,提问作者Rakesh D
相关产品推荐
相关产品推荐

