Specs2中如何Mock已注入类的单个依赖模块?
问题解答
1. 用copy方法替换依赖是否可行?
这种方式仅当ExampleClass是Scala case class时才有可能生效,否则编译阶段就会报错(普通类不会自动生成copy方法),同时还存在以下明显局限:
- 构造参数的
implicit修饰会失效:copy方法会把原构造的隐式参数转为普通参数,你必须显式传入所有其他依赖(比如configuration、secondModule等)才能成功调用copy,否则会出现参数缺失的编译错误。 - 无法覆盖构造阶段的依赖调用:如果
ExampleClass在构造过程中就调用了thirdModule的方法(比如初始化内部状态),copy替换的是构造完成后的实例,之前的初始化逻辑已经用了容器注入的原thirdModule,mock不会生效。 - 脱离容器管理:原类标注了
@Singleton,但copy生成的实例是手动创建的,不再受DI容器的单例管理,和容器中instanceOf获取的实例是两个完全独立的对象,测试时必须确保使用的是copyExampleClass而非原exampleClass。
综上,这种方式不是可靠的测试依赖替换方案。
2. 公共trait复用测试逻辑是否可行?
完全可行,这是Play + Specs2测试中复用公共配置的常规做法:
- trait中的
lazy val会在每个测试类实例化时延迟初始化,能保证测试之间的隔离性,避免不同测试互相干扰。 - 如果不同测试类需要定制mock逻辑,可以在trait中定义抽象方法或可重写的
val,让子类自行实现。 - 注意隐式参数的作用域:确保trait中的隐式实例(比如
copyExampleClass)在测试类中能被正确解析,避免和测试类内部的隐式变量冲突。
推荐的正确做法
更可靠的方式是直接通过Play的GuiceApplicationBuilder覆盖DI绑定,让容器直接注入mock实例,无需依赖copy方法:
// 在CommonProps trait中 val mockedThirdModule = Mockito.mock(classOf[ThirdModule]) // 提前配置mock行为 when(mockedThirdModule.foo).thenReturn(Future.successful("ABC")) // 构建自定义应用,覆盖ThirdModule的绑定 lazy val app = new GuiceApplicationBuilder() .overrides(bind[ThirdModule].toInstance(mockedThirdModule)) .build() // 直接从容器获取注入了mock的ExampleClass实例 implicit lazy val exampleClass = app.injector.instanceOf[ExampleClass]
这种方式的优势:
- 不管
ExampleClass是不是case class都能生效。 - 确保构造阶段就使用mock的依赖,避免初始化逻辑的问题。
- 实例依然受容器管理,符合原类的
@Singleton语义(测试容器内是单例)。
内容的提问来源于stack exchange,提问作者Shivam Sahil
相关产品推荐
相关产品推荐

