Play 2.6.x中带构造注入的PlaySpec测试类无法被检测的问题
为什么PlaySpec测试类用@Inject()构造函数不被检测到?怎么优雅注入依赖?
这个问题我之前在Play 2.6项目里也碰到过,咱们先搞清楚为什么第一种写法无效,再给你几个更优雅的替代方案:
原因:测试类的实例化机制不支持@Inject()构造函数
Play的测试框架(结合ScalaTest的GuiceOneAppPerSuite)是基于常规的测试运行器(比如sbt test的runner)工作的——测试运行器会直接调用测试类的无参构造函数来创建实例,完全不会通过Guice注入器来初始化测试类。
你第一种写法给测试类加了带@Inject()的构造函数,测试运行器找不到无参构造函数,就根本不会识别这个测试类,自然不会执行里面的测试用例。
优雅的替代方案
既然知道了原因,咱们可以用Play测试框架提供的原生特性来实现优雅注入,完全不用碰弃用的Play.current:
方案1:用Injecting特质的inject[T]()方法(最简洁)
你已经混入了Injecting特质,它自带了inject[T]()方法,可以直接在测试类级别或者测试用例里获取Guice管理的依赖:
class AuthControllerTest extends PlaySpec with GuiceOneAppPerSuite with Injecting { // 在测试类级别注入,所有测试用例共享这个实例 private val userRepository: UserRepository = inject[UserRepository] "Foo bar" should { "do xyz" in { // 直接使用注入好的userRepository val result = userRepository.findUserById(1L) result mustBe Some(testUser) } } }
这个方法既简洁又符合Play的测试规范,完全替代了手动拿注入器的写法。
方案2:用GuiceOneAppWithComponents自定义组件(适合复杂场景)
如果你的测试需要自定义应用组件(比如替换生产依赖为测试实现),可以用GuiceOneAppWithComponents特质,自己构建组件并获取依赖:
class AuthControllerTest extends PlaySpec with GuiceOneAppWithComponents { override def components: BuiltInComponents = new BuiltInComponentsFromContext(context) { // 这里可以自定义你的组件,比如绑定测试用的UserRepository override lazy val userRepository: UserRepository = new TestUserRepository() } // 直接从components里拿到需要的依赖 private val userRepository = components.userRepository "Foo bar" should { "do xyz" in { userRepository.createUser(testUser) mustBe true } } }
这种方式适合需要完全控制应用组件的测试场景,比如隔离外部依赖的时候。
方案3:用测试模块替换依赖(适合全局替换)
如果多个测试类都需要替换同一个依赖,可以创建一个测试模块,在启动测试应用时加载它:
// 定义测试模块,替换生产的UserRepository为测试实现 class TestUserModule extends AbstractModule { override def configure(): Unit = { bind(classOf[UserRepository]).to(classOf[TestUserRepository]) } } // 测试类中加载这个模块 class AuthControllerTest extends PlaySpec with GuiceOneAppPerSuite with Injecting { // 覆盖fakeApplication,加载测试模块 override def fakeApplication(): Application = { GuiceApplicationBuilder() .load(new TestUserModule()) .build() } private val userRepository: UserRepository = inject[UserRepository] "Foo bar" should { "do xyz" in { // 这里的userRepository就是TestUserRepository的实例 userRepository.findUserById(1L) mustBe Some(testUser) } } }
这种方式适合在多个测试类中共享依赖替换规则,减少重复代码。
总结
- 测试类不能用
@Inject()构造函数,因为测试运行器不会通过Guice实例化测试类; - 最简洁的方案是用
Injecting特质的inject[T]()方法直接获取依赖; - 复杂场景可以用
GuiceOneAppWithComponents或测试模块来自定义组件。
内容的提问来源于stack exchange,提问作者Cedric Reichenbach
相关产品推荐
相关产品推荐

