Scala Play框架TDD测试:带参测试类传参与控制器测试编写
解决Scala Play框架中TDD测试类传递参数的问题
首先咱们梳理下你遇到的核心问题:你的测试类通过构造函数接收getController实例,但ScalaTest测试框架默认会尝试用无参构造创建测试类,所以传递参数时会执行失败。再加上Play框架的控制器是依赖注入模式(需要ControllerComponents和EmployeeDAO),手动实例化控制器也会非常麻烦。下面我给你一步步讲怎么正确编写这个测试。
核心问题分析
你的原测试类存在两个关键问题:
- ScalaTest默认要求测试类提供无参构造函数,带参数的测试类框架无法自动实例化
- Play的控制器依赖
ControllerComponents和EmployeeDAO,这些实例由Guice注入容器管理,不能直接手动new出来
正确的测试编写方式
我们需要借助scalatestplus-play提供的测试特质,它能帮我们处理Play应用的依赖注入,同时还能方便地mock依赖(比如EmployeeDAO)来做纯TDD单元测试。
1. 选择合适的测试特质
推荐使用GuiceOneAppPerTest,它会为每个测试方法创建一个独立的Play应用实例,保证测试之间的隔离性。如果需要所有测试共享一个应用实例,可以改用GuiceOneAppPerSuite。
2. 两种测试方案:Mock依赖 / 集成测试
方案一:Mock EmployeeDAO(纯单元测试,隔离数据库)
这种方式适合TDD早期阶段,只测试控制器的逻辑,不依赖真实数据库。需要用到mock库,比如Mockito,先在build.sbt里添加依赖:
libraryDependencies += "org.mockito" %% "mockito-scala" % "1.17.12" % Test
然后编写测试类(注意修正控制器和测试类的命名,符合Scala规范):
package controllers import org.scalatestplus.play._ import org.scalatestplus.mockito.MockitoSugar import play.api.libs.json.Json import play.api.mvc.Result import play.api.test.Helpers._ import dao.EmployeeDAO import scala.concurrent.Future class GetControllerSpec extends PlaySpec with GuiceOneAppPerTest with MockitoSugar { // Mock EmployeeDAO,隔离真实数据库依赖 private val mockEmployeeDAO = mock[EmployeeDAO] // 从Play测试应用中获取ControllerComponents实例 private val controllerComponents = app.injector.instanceOf[play.api.mvc.ControllerComponents] // 创建待测试的控制器,传入mock的DAO和组件 private val getController = new GetController(controllerComponents, mockEmployeeDAO) "GetController" should { "return OK with employee JSON when given a valid existing ID" in { // 准备测试用的员工实体(替换成你实际的Employee类结构) val testEmployee = Employee(id = 6, name = "Test User", position = "Developer") // 模拟DAO的查询行为:传入ID6时返回该员工 when(mockEmployeeDAO.getEmployeestructure(6L)).thenReturn(Future.successful(Some(testEmployee))) // 调用控制器的get方法,传入模拟请求 val result: Future[Result] = getController.get(6).apply(FakeRequest()) // 断言结果:状态码为OK,返回的JSON与测试数据一致 status(result) mustBe OK contentAsJson(result) mustBe Json.toJson(testEmployee) } "return NOT_FOUND when given an ID that doesn't exist" in { // 模拟DAO查询无结果的情况 when(mockEmployeeDAO.getEmployeestructure(999L)).thenReturn(Future.successful(None)) val result = getController.get(999).apply(FakeRequest()) // 断言返回404状态码 status(result) mustBe NOT_FOUND } } }
方案二:使用测试数据库(集成测试)
如果你想测试完整的流程(控制器→DAO→数据库),可以配置Play连接专门的测试数据库(比如H2内存数据库),直接注入真实的依赖进行测试:
首先在conf/application.conf中添加测试环境的数据库配置:
# 测试环境专属配置 play.http.secret.key="test-secret-key" db.default.driver=org.h2.Driver db.default.url="jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_DELAY=-1" db.default.username=sa db.default.password=""
然后编写集成测试类:
package controllers import org.scalatestplus.play._ import play.api.libs.json.Json import play.api.mvc.Result import play.api.test.Helpers._ import dao.EmployeeDAO import scala.concurrent.Future class GetControllerIntegrationSpec extends PlaySpec with GuiceOneAppPerTest { // 直接从测试应用中注入真实的控制器和DAO实例 private val getController = app.injector.instanceOf[GetController] private val employeeDAO = app.injector.instanceOf[EmployeeDAO] // 测试前插入测试数据 override def beforeEach(): Unit = { super.beforeEach() // 调用DAO的插入方法添加测试员工(替换成你实际的插入逻辑) await(employeeDAO.insert(Employee(6, "Test User", "Developer"))) } // 测试后清理数据 override def afterEach(): Unit = { await(employeeDAO.delete(6)) super.afterEach() } "GetController" should { "return OK with employee JSON for existing ID" in { val result: Future[Result] = getController.get(6).apply(FakeRequest()) status(result) mustBe OK val employeeJson = contentAsJson(result) (employeeJson \ "id").as[Long] mustBe 6 (employeeJson \ "name").as[String] mustBe "Test User" } } }
额外注意事项
- 你的原控制器类名建议改成
GetController(首字母大写,符合Scala命名规范) - 原测试中
should be (null)的断言是错误的:控制器的get方法返回Action.async,调用后会返回Future[Result],永远不会是null,应该断言HTTP状态码和返回的JSON内容
内容的提问来源于stack exchange,提问作者Indu Chabada
相关产品推荐
相关产品推荐

