Scala Mock对象创建问题:val成员无法被Mockito注入Mock实例
问题根源与解决方案
这个问题的核心原因很直接:Mockito的@InjectMocks无法替换Scala中用val定义的字段。因为Scala的val编译后会对应Java的private final字段,只有getter方法没有setter,Mockito无法通过常规反射手段修改它的值;而var是可变变量,生成的字段带有setter,所以Mock实例能被成功注入。
下面给你两种可行的解决方案,优先推荐第一种(符合最佳实践):
方案1:使用构造函数注入(推荐)
这是最符合依赖注入原则的做法,把依赖B通过构造函数传入A,既保证了不可变性(依然用val),又能轻松替换依赖进行测试。
修改业务类A
class A(val b: B = new B) { // 给b设置默认值,原有业务代码无需改动 def sum(a: Int): Int = { b.add(a) } }
修改测试类ATest
你甚至可以不用@InjectMocks,直接把Mock的B实例传给A的构造函数,代码更清晰:
class ATest extends FunSuite with BeforeAndAfterAll { @Mock val b: B = new B override def beforeAll(): Unit = { super.beforeAll() MockitoAnnotations.initMocks(this) } test("testSum") { Mockito.doReturn(20).when(b).add(12) val a = new A(b) // 直接注入Mock实例 val actualValue = a.sum(12) assertResult(20)(actualValue) } }
如果坚持用@InjectMocks,构造函数注入也能正常工作,Mockito会自动识别构造函数参数并注入Mock实例:
class ATest extends FunSuite with BeforeAndAfterAll { @InjectMocks val a: A = new A() // 无需手动传入b,Mockito会自动注入 @Mock val b: B = new B override def beforeAll(): Unit = { super.beforeAll() MockitoAnnotations.initMocks(this) } test("testSum") { Mockito.doReturn(20).when(b).add(12) val actualValue = a.sum(12) assertResult(20)(actualValue) } }
方案2:反射替换val字段(不推荐,应急用)
如果实在不想修改业务类的结构,可以用Scala反射强行修改val字段的值,但这种方式破坏了封装性,代码也不够优雅,只建议临时应急:
import scala.reflect.runtime.universe._ class ATest extends FunSuite with BeforeAndAfterAll { @Mock val b: B = new B val a: A = new A override def beforeAll(): Unit = { super.beforeAll() MockitoAnnotations.initMocks(this) // 用反射替换a中的b字段 val mirror = runtimeMirror(a.getClass.getClassLoader) val instanceMirror = mirror.reflect(a) val fieldSymbol = typeOf[A].decl(TermName("b")).asTerm val fieldMirror = instanceMirror.reflectField(fieldSymbol) fieldMirror.set(b) } test("testSum") { Mockito.doReturn(20).when(b).add(12) val actualValue = a.sum(12) assertResult(20)(actualValue) } }
总结
优先选择构造函数注入,它不仅解决了测试问题,还让代码的依赖关系更清晰,符合面向对象设计的最佳实践。反射的方式尽量少用,除非有特殊限制无法修改业务代码。
内容的提问来源于stack exchange,提问作者Dev
相关产品推荐
相关产品推荐

