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

Scala依赖Mock时遇空指针异常问题求助

解决ScalaMock创建依赖注入类Mock时的空指针异常

问题场景

我有以下基于依赖注入的Scala类:

class Service2  @Inject()(){
  val REDIS_URL = "some constant"
}

class Service1  @Inject()(service2: Service2){
  val redisStr = service2.REDIS_URL
  def getUrl = redisStr
}

class TestService @Inject()(service1: Service1){
  def foo() = service1.getUrl
}

测试TestService.foo时,用ScalaTest的MockFactory创建Service1的Mock:

class TestServiceTest extends org.scalatest.AsyncFunSuite with MockFactory  {
   val service1Mock = mock[Service1]
   // ...测试逻辑
}

初始化测试用例时触发了空指针异常,原因是生成Mock时Service1的初始化代码会执行,此时构造注入的service2为null。即使把Service1构造器改成柯里化形式,只要类里存在初始化时依赖service2的代码,还是会触发NPE:

class Service1 @Inject()()(service2: Service2) {
  def url = service2.REDIS_URL
  
  config.useSingleServer()
        .setAddress(service2.REDIS_URL) // 初始化时执行,service2为null导致NPE
}

解决办法

1. 重构Service1,延迟初始化依赖逻辑

把需要依赖service2的初始化代码改成延迟加载,比如用lazy val或者移到方法中,这样Mock初始化时不会执行这段代码,只有当实际调用相关方法时才会触发:

class Service1 @Inject()(service2: Service2) {
  def url = service2.REDIS_URL
  
  // 用lazy val延迟初始化config,避免在类加载时执行
  private lazy val config = {
    val cfg = new RedisConfig() // 假设这是你的配置类
    cfg.useSingleServer().setAddress(service2.REDIS_URL)
    cfg
  }
  
  // 需要用到config的方法
  def doSomethingWithRedis() = {
    // 使用config
  }
}

2. Mock底层依赖而非上层服务

不要直接Mock Service1,而是Mock它的依赖Service2,然后实例化真实的Service1注入到TestService中。这样既测试了TestService的逻辑,又能保证Service1的初始化逻辑正常执行:

class TestServiceTest extends AsyncFunSuite with MockFactory {
  // Mock Service2
  val service2Mock = mock[Service2]
  (service2Mock.REDIS_URL _).returns("test-redis-url")
  
  // 用mock的Service2实例化真实的Service1
  val realService1 = new Service1(service2Mock)
  
  // 注入到TestService
  val testService = new TestService(realService1)
  
  test("foo returns correct redis url") {
    assert(testService.foo() == "test-redis-url")
  }
}

3. 使用ScalaMock的构造参数传入依赖

如果必须Mock Service1,可以在创建Mock时通过constructorArgs参数传入有效的Service2实例,避免初始化时service2为null:

class TestServiceTest extends AsyncFunSuite with MockFactory {
  val service2Mock = mock[Service2]
  (service2Mock.REDIS_URL _).returns("mocked-url")
  
  // 传入构造参数创建Service1的Mock
  val service1Mock = mock[Service1](constructorArgs = Seq(service2Mock))
  
  // 可以按需Mock Service1的方法
  (service1Mock.getUrl _).returns("custom-mocked-url")
  
  val testService = new TestService(service1Mock)
  
  test("foo returns custom mocked url") {
    assert(testService.foo() == "custom-mocked-url")
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 23:54:42