以下代码应在出现意外情况时引发异常
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)))...
答案 0 :(得分:0)
由于是Future,因此该异常在userFutureOption
中异步引发,计算不在您的主线程中。
因此,该异常不会传播到测试结果。
您需要使用方法onComplete
:
userFutureOption onComplete {
case Success(user) => // OK
case Failure(t) => // KO
}
Here是有关Scala期货的一些文档。
答案 1 :(得分:0)
尽管我不明白为什么,但我确实能正常工作。 add
和remove
方法之间有一个很大的区别。
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
在未来中隐藏了未来,但我不知道为什么。我很乐意接受一个答案,该答案可以解释这里发生的事情。