You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Play 2.6测试ProjectController时出现NullPointerException求助

解决Play 2.6 + ReactiveMongo控制器实例化测试的NullPointerException问题

你的问题出在测试中对collection方法的mock方式上——直接mockFuture[JSONCollection]会导致链式调用时触发空指针,因为Mockito生成的Future mock并没有实现核心的异步方法。下面是具体的分析和修复方案:

问题根源

你在测试里写了:

override def collection: Future[JSONCollection] = mock[Future[JSONCollection]]

但Future是一个异步trait,Mockito创建的mock对象并不会自动实现flatMap这类方法。当控制器代码调用collection.flatMap(...)时,就会因为未初始化的方法抛出NullPointerException。另外,你也没有对集合的后续操作(find/cursor/collect)做正确的mock,导致整个调用链断裂。

修复后的测试代码

我们需要mockJSONCollection对象,并用Future.successful包装它,然后依次mock链式调用的每一步,让它们返回预期的测试数据:

class ProjectControllerSpec extends PlaySpec with GuiceOneAppPerTest with Injecting with MockitoSugar {

  val mockedReactiveMongoApi: ReactiveMongoApi = mock[ReactiveMongoApi]
  val mockedExecutionContext: ExecutionContext = mock[ExecutionContext]

  "ProjectController GET" should {
    "render the 'List of Projects' page from a new instance of controller" in {
      // 1. 准备测试用的Project列表(根据你的实际case class调整)
      val testProjects = List(
        Project("proj-1", "My First Project"),
        Project("proj-2", "My Second Project")
      )

      // 2. Mock ReactiveMongo的各个核心对象
      val mockedCollection: JSONCollection = mock[JSONCollection]
      val mockedFindObservable: FindObservable[JsObject] = mock[FindObservable[JsObject]]
      val mockedProjectedObservable: FindObservable[JsObject] = mock[FindObservable[JsObject]]
      val mockedCursor: Cursor[Project] = mock[Cursor[Project]]

      // 3. 配置mock对象的行为,模拟完整的查询链
      when(mockedCollection.find(Json.obj())).thenReturn(mockedFindObservable)
      when(mockedFindObservable.projection(Json.obj())).thenReturn(mockedProjectedObservable)
      when(mockedProjectedObservable.cursor[Project]()).thenReturn(mockedCursor)
      when(mockedCursor.collect[List](-1, Cursor.FailOnError[List[Project]]()))
        .thenReturn(Future.successful(testProjects))

      // 4. 创建控制器实例,重写collection返回已准备好的mock集合
      val controller: ProjectController = new ProjectController(stubControllerComponents(), mockedReactiveMongoApi)(mockedExecutionContext) {
        override def collection: Future[JSONCollection] = Future.successful(mockedCollection)
      }

      // 5. 执行测试断言
      val result = controller.listProjects().apply(FakeRequest(GET, "/projects"))
      status(result) mustBe OK
      contentType(result) mustBe Some("text/html")
      contentAsString(result) must include ("Projects")
    }
  }
}

额外提示

  • 如果不需要验证find或projection的具体参数,可以用any()替代Json.obj(),比如when(mockedCollection.find(any[JsObject])).thenReturn(mockedFindObservable),让测试更灵活。
  • 确保你的Project case class有对应的JSON格式定义(比如implicit val projectFormat: OFormat[Project]),否则ReactiveMongo的cursor[Project]会出错。
  • 永远不要直接mockFuture、Option这类基础容器类型,而是用它们的工厂方法(比如Future.successful、Some)来包装mock对象,这样容器的方法才能正常工作。

内容的提问来源于stack exchange,提问作者consuela

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:30:37