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

如何使用Mockito和ScalaTest模拟Scala伴生对象属性

问题根因

Scala 中定义的object单例编译后会生成final修饰的类,原生Mockito默认不支持mock final类;同时最初实现里的val pool : DataSource = getDataSource会在伴生对象类加载初始化阶段直接执行真实的getDataSource逻辑,后续mock操作赶不上这个初始化时机,自然无法命中mock逻辑。

可行解决方案

方案1:开启Mockito final mock支持 + 懒加载改造

如果要保留全局单例的写法,按以下步骤调整:

  1. 开启Mockito对final类的mock支持:在测试资源目录创建文件src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker,文件内容填写:
mock-maker-inline
  1. 调整CoreDataSource的连接池初始化逻辑为懒加载,避免类加载阶段就触发真实连接池创建:
object CoreDataSource {
  // lazy修饰后,首次访问pool时才会执行初始化,给mock留出切入时机
  lazy val pool : DataSource = getDataSource
  def getDataSource: DataSource = {
    // 真实的连接池创建逻辑
  }
}
  1. 测试代码中使用mockito-scala提供的withObjectMocked作用域包裹测试逻辑,对单例对象打桩:
@RunWith(classOf[JUnitRunner])
class MySuite extends AnyFunSuite with MockitoSugar with Matchers {
  test("Test mocking the Datasource") {
    withObjectMocked[CoreDataSource.type]{
      val mockDs = mock[DataSource]
      val mockConn = mock[Connection]
      // 配置mock返回逻辑
      when(CoreDataSource.getDataSource).thenReturn(mockDs)
      when(mockDs.getConnection).thenReturn(mockConn)
      // 执行测试逻辑
      val query = new Query()
      val result = query.getSalutations
      // 断言与验证
      assert(result == "")
      verify(mockDs).getConnection
    }
  }
}

之前尝试的Option版本懒加载逻辑也可以正常使用,注意确保getDataSource不会在mock生效前被触发即可,Scala原生的lazy val是线程安全的懒加载实现,比手动写Option判断更简洁。

方案2:依赖注入改造(生产环境推荐)

从架构层面解耦,不要在业务类中硬依赖全局静态对象,通过构造器传入DataSource依赖,从根源上避免mock静态对象的问题:

// 将DataSource作为构造参数传入,业务代码不关心实例来源
class Query(source: DataSource) {
  def getSalutations: String = {
    val conn : Connection = source.getConnection
    // 业务逻辑
  }
}
// 生产环境代码直接传入全局连接池即可
val productionQuery = new Query(CoreDataSource.pool)

测试时直接传入mock的DataSource实例即可,完全不需要操作静态单例,不会遇到final类mock的问题:

@RunWith(classOf[JUnitRunner])
class MySuite extends AnyFunSuite with MockitoSugar with Matchers {
  test("Test getSalutations without real datasource") {
    val mockDs = mock[DataSource]
    val mockConn = mock[Connection]
    when(mockDs.getConnection).thenReturn(mockConn)
    // 直接传入mock依赖
    val query = new Query(mockDs)
    val result = query.getSalutations
    assert(result == "")
    verify(mockDs).getConnection
  }
}

该方案不需要依赖Mockito的高级mock特性,测试逻辑稳定无黑魔法,是行业内通用的可测试性最佳实践。

方案3:增加测试覆写入入口

如果不想调整现有依赖逻辑,可以给CoreDataSource增加测试专用的实例设置入口,测试时直接替换连接池为mock实例,不需要mock静态object本身:

object CoreDataSource {
  private var _pool: DataSource = _
  // 仅测试阶段调用,替换连接池实例
  def setTestDataSource(ds: DataSource): Unit = {
    _pool = ds
  }
  def pool: DataSource = {
    if (_pool == null) {
      _pool = getDataSource
    }
    _pool
  }
  private def getDataSource: DataSource = {
    // 真实连接池创建逻辑
  }
}

测试时直接调用CoreDataSource.setTestDataSource(mockDs)传入提前准备好的mock对象即可,实现简单,不会触发final类mock的报错。


内容的提问来源于stack exchange,提问作者Jackie

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 19:48:34