如何在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
相关产品推荐
相关产品推荐

