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

