代码在单元测试中不引发异常

时间:2019-07-18 19:14:10

标签: scala scalatest playframework-2.6

以下代码应在出现意外情况时引发异常

     def remove(loginInfo: LoginInfo): Future[Unit] = Future{
        println("In PasswordRepository, removing password")//TODOM - any print statements should not reveal confidential information
        val bucketId = utilities.bucketIDFromEmail(loginInfo.providerKey)
        val userFutureOption = userRepo.findOne(UserKeys(bucketId,loginInfo.providerKey,loginInfo))
        userFutureOption.flatMap(userOption =>{ userOption match {
          case Some(user) => {
            println("setting password info to None") //TODOM - need to check that passwordInfo isn't empty

val updatedUser = User(user.id,
              UserProfile(Some(InternalUserProfile(user.profile.internalProfileDetails.get.loginInfo,
                bucketId,
                user.profile.internalProfileDetails.get.confirmed,
                None)),ExternalUserProfile(user.profile.externalProfileDetails.email,
                user.profile.externalProfileDetails.firstName,
                user.profile.externalProfileDetails.lastName,
                user.profile.externalProfileDetails.password))) //don't need to store password explicitly. It is in PasswordInfo field already
            println("updated user "+updatedUser)
            val userFutureOption = userRepo.update(updatedUser)
            userFutureOption.map(userOption => {
              userOption match {//do nothing in Some as the function returns Unit
                case Some(user) => {
                  Unit
                }
                case None => {
                  println("error in deleting password info of the user")
                  //TODOM - funtion is not throwing the Exception. Need to check
                  throw new Exception(messagesApi("error.passwordDeleteError")(langs.availables(0))) 
                }
              }
            })
          }
          case None => {
            println("user not found. Can't remove password info. This shouldn't have happened")
            throw new Exception(messagesApi("error.passwordDeleteError")(langs.availables(0)))
          }
        }
        })
      }

当我对代码进行单元测试时,代码不会引发异常。为什么?

  "PasswordRepository Specs" should {
    "should return error if password cannot be deleted for an existing user" in {

      val user = repoTestEnv.testEnv.user 
      when(repoTestEnv.mockUserRepository.findOne(ArgumentMatchers.any())).thenReturn(Future{Some(repoTestEnv.testEnv.user)})
      when(repoTestEnv.mockUserRepository.update(ArgumentMatchers.any())).thenReturn(Future{None}) //this should trigger the throw exception code
      val passwordRepository = new PasswordRepository(repoTestEnv.testEnv.mockHelperMethods,repoTestEnv.mockUserRepository,repoTestEnv.testEnv.messagesApi,repoTestEnv.testEnv.langs)
          val exception = intercept[java.lang.Exception]( await[Unit](passwordRepository.remove(repoTestEnv.testEnv.loginInfo))(Timeout(Duration(5000,"millis"))))
      println(s"exception is ${exception}")
      exception.getMessage() mustBe repoTestEnv.testEnv.messagesApi("error.passwordDeleteError")(repoTestEnv.testEnv.langs.availables(0))

    }
  }

我得到了错误

=== starting new test case execution ====
In PasswordRepository, removing password
setting password info to None
updated user User(11111111-1111-1111-1111-111111111111,UserProfile(Some(InternalUserProfile(LoginInfo(credentials,test@test.com),1,true,None)),ExternalUserProfile(test@test.com,fn,ln,Some(somePassword))))
error in deleting password info of the user
=== ending test case execution ====

Expected exception java.lang.Exception to be thrown, but no exception was thrown

更新 该行为是特殊的,因为单元测试适用于add方法,该方法与remove方法非常相似。

 def add(loginInfo: LoginInfo, authInfo: PasswordInfo): Future[PasswordInfo] = {
    println(s"in PasswordRepository add ${loginInfo.providerID}, ${loginInfo.providerKey}, ${authInfo.hasher}, ${authInfo.password},${authInfo.salt}")
    val bucketId = utilities.bucketIDFromEmail(loginInfo.providerKey)
    val userFutureOption = userRepo.findOne(UserKeys(bucketId,loginInfo.providerKey,loginInfo))
    userFutureOption.flatMap(userOption =>{ userOption match {
      case Some(user) => {
        println("adding password info "+ authInfo+ "to user "+user) //TODOM - need to check that passwordInfo isn't empty
        val updatedUser = User(user.id,
          UserProfile(Some(InternalUserProfile(user.profile.internalProfileDetails.get.loginInfo,
            bucketId,
            user.profile.internalProfileDetails.get.confirmed,
            Some(authInfo))),ExternalUserProfile(user.profile.externalProfileDetails.email,
            user.profile.externalProfileDetails.firstName,
            user.profile.externalProfileDetails.lastName,
            user.profile.externalProfileDetails.password))) //don't need to store password explicitly. It is in PasswordInfo field already
        println("updated user "+updatedUser)
        //TODOM is there a risk if email id gets updated. Then it should be updated in both email and loginInfo
        val userUpdateFutureOption = userRepo.update(updatedUser)
        userUpdateFutureOption.map(userOption => {
          userOption match {
          case Some(user) => {
            //TODOM - should not access PasswordInfo directly
            println("returning PassswordInfo "+user.profile.internalProfileDetails.get.passwordInfo)
            //TODOM - check for empty for both internalProfileDetails and passwordInfo in Functional way
            user.profile.internalProfileDetails.get.passwordInfo.get
          }
          case None => {
            println("error in updating password info of the user")
            //authInfo //TODOM - I should throw an exception from this Future.
            throw new Exception(messagesApi("error.passwordConfigureError")(langs.availables(0)))
          }
        }
        })
      }
      case None => {
        println("user not found. Can't set password info. This shouldn't have happened")
        throw new Exception(messagesApi("error.passwordConfigureError")(langs.availables(0)))
      }
    }
    })
  }

以下测试用例通过了add

  "PasswordRepository Specs" should {
    "should return error if password cannot be updated for an existing user when adding a password" in {

      val newPassword = PasswordInfo("newHasher","newPassword",Some("newSalt"))
      val user = repoTestEnv.testEnv.user

      when(repoTestEnv.mockUserRepository.findOne(ArgumentMatchers.any())).thenReturn(Future{Some(repoTestEnv.testEnv.user)})
      when(repoTestEnv.mockUserRepository.update(ArgumentMatchers.any())).thenReturn(Future{None})
      val passwordRepository = new PasswordRepository(repoTestEnv.testEnv.mockHelperMethods,repoTestEnv.mockUserRepository,repoTestEnv.testEnv.messagesApi,repoTestEnv.testEnv.langs)

      println(s"adding password ${newPassword}")
      val exception = intercept[java.lang.Exception]( await[PasswordInfo](passwordRepository.add(repoTestEnv.testEnv.loginInfo,newPassword))(Timeout(Duration(5000,"millis"))))
      println(s"exception is ${exception}")
      exception.getMessage() mustBe repoTestEnv.testEnv.messagesApi("error.passwordConfigureError")(repoTestEnv.testEnv.langs.availables(0))

    }
  }

该函数似乎根本不抛出任何异常,因为这样做也不起作用

val userUpdateFutureOption = userRepo.update(updatedUser)
        throw new Exception(messagesApi("error.passwordDeleteError")(langs.availables(0)))

        userUpdateFutureOption.map(userOption => {...}

但是如果我在代码的开头抛出异常,则测试通过

val userFutureOption = userRepo.findOne(UserKeys(bucketId,loginInfo.providerKey,loginInfo))
    throw new Exception(messagesApi("error.passwordDeleteError")(langs.availables(0)))...

2 个答案:

答案 0 :(得分:0)

由于是Future,因此该异常在userFutureOption中异步引发,计算不在您的主线程中。 因此,该异常不会传播到测试结果。

您需要使用方法onComplete

userFutureOption onComplete {
  case Success(user) => // OK
  case Failure(t) => // KO
}

Here是有关Scala期货的一些文档。

答案 1 :(得分:0)

尽管我不明白为什么,但我确实能正常工作。 addremove方法之间有一个很大的区别。

def add(...):Future[PasswordInfo] = {...}

def remove(...):Future[Unit] = Future {...} //notice the Future

我一直怀疑在Unit中使用remove。为了进行实验,我将方法的返回值更改为Future [Int]

def remove1:Future[Int] = Future[...] = 
{

...
userUpdateFutureOption.map(userOption => {
          userOption match {//do nothing in Some as the function returns Unit
            case Some(user) => {
              //println("password removed")
              1
            }
...
} 

并且代码停止编译抱怨

Error:(171, 29) type mismatch;
 found   : scala.concurrent.Future[Int]
 required: Int
    userFutureOption.flatMap(userOption =>{ userOption match {

将鼠标悬停在代码上会显示错误Expression of type Future[Future[Int]] doesn't conform to type Future[Int]

似乎Unit在未来中隐藏了未来,但我不知道为什么。我很乐意接受一个答案,该答案可以解释这里发生的事情。