在单元测试中传递给控制器​​时,FakeRequest 似乎为 Null

FakeRequest seem to be Null when passed to a controller in unit test

单元测试控制器时出现 Null Pointer 错误。问题似乎在行

def signupUser = Action.async{
    implicit request => { //requeust seem to be null

我怀疑是这样,因为之前测试的堆栈跟踪指向隐式请求行。但是我不知道这有什么问题,因为我像这样使用 FakeRequest val request = FakeRequest("POST", "/ws/users/signup").withJsonBody(Json.parse("""{"bad": "field"}"""))

以下是我要进行单元测试的控制器片段

  class UserController @Inject()(userRepo: UsersRepository,cc: ControllerComponents, silhouette: Silhouette[JWTEnv])(implicit exec: ExecutionContext) extends AbstractController(cc){ 

def signupUser = Action.async{
    implicit request => {...}
 }

我只想测试控制器 returns 在收到没有 json 正文的请求时出错。因此我不需要 Silhouette 并且我想模拟它。但是我收到空指针错误。

以下是我编写单元测试用例的方式

class UserControllerUnitSpec extends PlaySpec with MockitoSugar {

  "User signup request with non-JSON body" should {

    "return  400 (Bad Request) and the validation text 'Incorrect body type. Body type must be JSON'" in {

      val email = "d@d.com"
      val loginInfo = LoginInfo(CredentialsProvider.ID, email);
      val passwordInfo = PasswordInfo("someHasher","somePassword",Some("someSalt"))
      val internalUserProfile = InternalUserProfile(loginInfo,true,Some(passwordInfo))
      val externalUserProfile = ExternalUserProfile(email,"d","d",Some("somePassword"))
      val userProfile = UserProfile(Some(internalUserProfile),externalUserProfile)
      val user = User(UUID.randomUUID(),userProfile)

      println("testing with mocked User value",user);

      val mockUserRepository = mock[UsersRepository]
      when(mockUserRepository.findUser(loginInfo)).thenReturn(Future(Some(user)))
      when(mockUserRepository.saveUser(user)).thenReturn(Future(Some(user)))

      val mockSilhouette = mock[Silhouette[JWTEnv]] //I am probably not doing this correctly
      val mockControllerComponents = mock[ControllerComponents] //I am not sure if this is correct either
      val controller = new UserController(mockUserRepository,mockControllerComponents,mockSilhouette)

      val result:Future[Result] = controller.signupUser(FakeRequest())
      (result.map(response => {
        println("response: ",response)
        response mustBe BadRequest
      }))
    }
  }
}

关于 mockControllerComponents,可以使用 Helpers.stubControllerComponents 代替模拟:

val mockControllerComponents = Helpers.stubControllerComponents()

关于 mockSilhouette,您必须使用 when(...).thenReturn(...) 设置模拟,类似于您为 mockUserRepository 所做的那样,即检查 [=17= 的所有用法] 在 signupUser 内并提供适当的方法存根:

val mockSilhouette = mock[Silhouette[JWTEnv]]
when(mockSilhouette.foo(...)).thenReturn(...)
when(mockUserRepository.bar(...)).thenReturn(...)
...

(代表问题作者发布解决方案).

这是有效的答案。谢谢马里奥。

class UserControllerUnitSpec extends PlaySpec /*with MockitoSugar*/ {

  "User signup request with non-JSON body" should {

    "return  400 (Bad Request) and the validation text 'Incorrect body type. Body type must be JSON'" in {

      val email = "d@d.com"
      val loginInfo = LoginInfo(CredentialsProvider.ID, email);
      val passwordInfo = PasswordInfo("someHasher","somePassword",Some("someSalt"))
      val internalUserProfile = InternalUserProfile(loginInfo,true,Some(passwordInfo))
      val externalUserProfile = ExternalUserProfile(email,"d","d",Some("somePassword"))
      val userProfile = UserProfile(Some(internalUserProfile),externalUserProfile)
      val user = User(UUID.randomUUID(),userProfile)

      println("testing with mocked User value",user);

      val mockUserRepository = mock(classOf[UsersRepository])
      // when(mockUserRepository.findUser(loginInfo)).thenReturn(Future(Some(user)))
     // when(mockUserRepository.saveUser(user)).thenReturn(Future(Some(user)))

     // val mockSilhouette = mock(classOf[Silhouette[JWTEnv]])
      val mockControllerComponents = Helpers.stubControllerComponents()//mock(classOf[ControllerComponents])
      /*
      The controller needs Silhouette. Using Silhouette's test kit to create fake instances.
      If you would like to test this controller, you must provide an environment that can handle your Identity and Authenticator implementation.
      For this case Silhouette provides a FakeEnvironment which automatically sets up all components needed to test your specific actions.

      You must only specify one or more LoginInfo -> Identity pairs that should be returned by calling request.identity in your action and
      the authenticator instance that tracks this user.
       */

      //User extends Identity trait
      /*
      Under the hood, the environment instantiates a FakeIdentityService which stores your given identities and returns it if needed.
      It instantiates also the appropriate AuthenticatorService based on your defined Authenticator type. All Authenticator services are real
      service instances set up with their default values and dependencies.
       */
      implicit val sys = ActorSystem("MyTest")
      implicit val mat = ActorMaterializer()
      implicit val env = FakeEnvironment[JWTEnv](Seq(loginInfo->user))
      val defaultParser = new mvc.BodyParsers.Default()
      val securedAction = new DefaultSecuredAction(new DefaultSecuredRequestHandler(new DefaultSecuredErrorHandler(stubMessagesApi())),defaultParser)
      val unsecuredAction = new DefaultUnsecuredAction(new DefaultUnsecuredRequestHandler(new DefaultUnsecuredErrorHandler(stubMessagesApi())),defaultParser)
      val userAware = new DefaultUserAwareAction(new DefaultUserAwareRequestHandler(),defaultParser)
      val mockSilhouette = new SilhouetteProvider[JWTEnv](env,securedAction,unsecuredAction,userAware)

      val controller = new UserController(mockUserRepository,mockControllerComponents,mockSilhouette)

      val request = FakeRequest("POST","ws/users/signup")
      println("sending request",request)
      //val result = controller.someMethod()
      val result:Future[Result] = controller.signupUser(request)

      status(result) mustBe BAD_REQUEST

    }
  }
}