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

如何在Scala中使用Mockito-Scala模拟构造函数做单元测试

解决方案

首先,不建议直接模拟构造函数,因为这会让测试和实现细节深度耦合,后续代码改动极易导致测试失效。更优雅的方式是通过依赖注入解耦,先看这种推荐方案:

方案1:重构代码实现依赖注入

把B的实例化逻辑从A的run方法中抽离,改为可外部注入或可重写的创建逻辑:

重构后的业务代码

import org.slf4j.LoggerFactory
import ch.qos.logback.classic.Logger

trait Logging {
  protected val logger: Logger = LoggerFactory.getLogger(getClass).asInstanceOf[Logger]
}

class B(s: String) {
  def call(): Unit = {
    // 实际业务逻辑
  }
}

case class A() extends Logging with Serializable {
  // 预留可替换的B创建入口,测试时可重写
  protected def createB(s: String): B = new B(s)

  def run(): Unit = {
    val b = createB("value")
    b.call()
  }
}

对应的单元测试

利用mockito-scala的mock能力,重写A的createB方法返回mock的B,直接stubcall方法的执行逻辑:

import org.scalatest.BeforeAndAfterEach
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import org.mockito.MockitoSugar

class ATest extends AnyWordSpec with Matchers with MockitoSugar with BeforeAndAfterEach {
  "A's run method" should {
    "trigger B's call method without executing real logic" in {
      // 创建mock的B实例
      val mockB = mock[B]
      // 构造A的子类,重写createB返回mock实例
      val a = new A {
        override def createB(s: String): B = mockB
      }

      // 执行run方法
      a.run()

      // 验证call方法被调用,且因为用了mock,实际业务逻辑不会执行
      verify(mockB).call()
    }
  }
}

方案2:直接模拟构造函数(仅用于无法重构的场景)

如果实在无法修改原有业务代码,可以使用mockito的mockConstruction API(mockito-scala完全兼容该能力),拦截B的构造函数,强制返回mock实例:

单元测试代码

import org.scalatest.BeforeAndAfterEach
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import org.mockito.MockitoSugar
import org.mockito.Mockito.{mockConstruction, verify}

class ATest extends AnyWordSpec with Matchers with MockitoSugar with BeforeAndAfterEach {
  "A's run method" should {
    "stub B's call method via constructor mocking" in {
      // 拦截所有B的构造行为,返回mock实例并stub call方法
      val mockConstructor = mockConstruction[B] { mockB =>
        doNothing.when(mockB).call()
      }

      val a = A()
      a.run()

      // 获取构造出的mockB实例,验证call方法被触发
      val mockB = mockConstructor.constructed.head
      verify(mockB).call()

      // 关闭构造拦截,避免影响其他测试逻辑
      mockConstructor.close()
    }
  }
}

注意事项

  • 构造函数拦截会作用于当前测试的整个作用域,所有新创建的B实例都会是mock,使用后必须手动关闭。
  • 这种方式耦合度极高,后续B的构造参数或初始化逻辑变化时,测试必须同步修改,维护成本高。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 02:00:08