You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 08:22:58