Scala中Mockito Mock Trait方法后子类调用失效问题排查与解决
问题描述
我尝试Mock一个调用外部服务的方法,为排查根因简化了问题场景。我发现可以Mock trait中的方法,但继承该trait的类调用此方法时Mock不生效,这是为什么?该如何解决?
想要Mock的login方法代码如下:
case class User(id: Int, name: String) trait LoginService { def login(name: String, password: String): Option[User] = Some(User(1, "anne")) } class OtherService extends LoginService { def getId: Int = { val getPassword="xx" // 更多待测试逻辑... login("Anne", getPassword).get.id } }
Scala测试中使用Mockito进行Mock的代码如下:
class MySpec extends AnyWordSpec with Matchers with MockitoSugar { "test3" in { val service = mock[LoginService] when(service.login("Anne", "xx")) thenReturn Some(User(222, "AA")) val other = new OtherService() other.getId should be(222) } "test2" in { val service = mock[OtherService] when(service.login("Anne", "xx")) thenReturn Some(User(222, "AA")) val other=new OtherService() other.getId should be(222) } "test1" in { val service = mock[LoginService] when(service.login("Anne", "xx")) thenReturn Some(User(222, "AA")) service.login("Anne","xx").get.id should be(222) } }
只有test1执行成功,而验证getId方法的test2和test3均失败,为何这两个测试无法模拟出id为222的用户?
依赖配置:
"org.scalatest" %% "scalatest" % "3.2.14" % Test, "org.mockito" %% "mockito-scala-scalatest" % "1.17.12" % Test,
原因分析
- test3失败原因:mock的是独立的
LoginService实例,但new OtherService()创建的是全新的、继承了LoginService的真实对象,它调用的是自身继承来的login方法,和mock的service实例无关联,不会使用mock返回值。 - test2失败原因:mock了
OtherService类,但new OtherService()创建的是另一个真实的OtherService对象,mock实例和真实对象是完全独立的两个个体,真实对象不会复用mock实例的行为配置。
解决方案
有两种常用解决方式,可根据场景选择:
方式1:依赖注入(推荐)
修改OtherService实现,不再直接继承LoginService,而是将其作为构造参数传入,测试时可轻松替换为mock实例:
// 修改后的业务代码 class OtherService(loginService: LoginService) { def getId: Int = { val getPassword = "xx" loginService.login("Anne", getPassword).get.id } }
对应的测试代码:
class MySpec extends AnyWordSpec with Matchers with MockitoSugar { "test with dependency injection" in { val mockLoginService = mock[LoginService] when(mockLoginService.login("Anne", "xx")) thenReturn Some(User(222, "AA")) val otherService = new OtherService(mockLoginService) otherService.getId should be(222) // 测试通过 } }
方式2:使用Spy进行部分Mock
若不想修改原有类结构,可使用Mockito的spy包装真实的OtherService实例,仅mock指定的login方法:
class MySpec extends AnyWordSpec with Matchers with MockitoSugar { "test with spy" in { val realService = new OtherService() val spyService = spy(realService) when(spyService.login("Anne", "xx")) thenReturn Some(User(222, "AA")) spyService.getId should be(222) // 测试通过 } }
注意:使用spy需谨慎,它会保留真实对象的大部分行为,仅替换指定方法,适合无法修改原有类结构的场景。
内容的提问来源于stack exchange,提问作者MrElephant
相关产品推荐
相关产品推荐

