【问题标题】:Future's .recover not getting called when Exception is thrown in Mockito unit test在 Mockito 单元测试中抛出异常时,未调用 Future 的 .recover
【发布时间】:2019-03-13 05:51:43
【问题描述】:

以下代码返回一个 Future。

val findUserFuture: Future[Option[User]] = userRepo.findOne(userKeys) 

然后我处理未来

findUserFuture.flatMap {....}
.recover{...}

fineOne 返回FutureFuturegetOneById 的调用

def findOne(userKeys:UserKeys):Future[Option[User]] = {
    Future{
      //val loginInfo:LoginInfo = LoginInfo(userKeys.providerID,userKeys.authProvider)
      val userOption:Option[User] = getOneById(userKeys)
      userOption
    }
  }

我想如果findOne 返回的Future 失败,即抛出异常,将调用recover。所以我通过让getOneById 抛出异常来模拟这一点。

when(mockUserRepository.findOne(userKeys)).thenReturn(Future(Some(user)))
      when(mockUserRepository.getOneById(userKeys)).thenThrow(classOf[RuntimeException]) //simulating database error

但是单元测试没有抛出异常,并且测试继续使用值Future(Some(User))

我也尝试从findOne - when(mockUserRepository.findOne(userKeys)).thenThrow(classOf[RuntimeException]) 抛出异常,但测试用例停止 与以下两个打印和Future.recover 未被调用

java.lang.RuntimeException was thrown.
java.lang.RuntimeException

【问题讨论】:

    标签: scala mockito scalatest


    【解决方案1】:

    这个findUserFuture: Future[Option[User]]userRepo.findOne 返回未来, 因此你需要在你的模拟中返回Future.failed。 例如。

    when(mockUserRepository.findOne(userKeys)).thenReturn(Future(Some(user)))
    when(mockUserRepository.getOneById(userKeys)).thenReturn(Future.failed(new RuntimeException("network failure"))
    

    在下面找到完整的工作测试来模拟您的用例:

    test("mock future test") {
        case class User(name: String)
        case class UserNotFoundException(name: String) extends Exception
        trait UserRepo {
          def findOne(name: String): Future[Option[User]]
        }
        val name      = "bob"
        val dummyUser = User("dummy")
    
        val userRepo = mock[UserRepo]
        when(userRepo.findOne(name)).thenReturn(Future.failed(new RuntimeException()))
    
        val userF = userRepo
          .findOne(name)
          .flatMap {
            case Some(user) ⇒ Future.successful(user)
            case None       ⇒ Future.failed(UserNotFoundException(name))
          }
          .recover {
            case NonFatal(_) ⇒ dummyUser
          }
    
        userF.futureValue shouldBe dummyUser
      }
    
    • 更新 * 仔细查看原帖后,我发现你嘲笑的方式有一个小错误。 试试下面:
    when(mockUserRepository.findOne(userKeys)).thenCallRealMethod()
    when(mockUserRepository.getOneById(userKeys)).thenThrow(classOf[RuntimeException]) 
    

    注意thenCallRealMethod() 这里,之前你在嘲笑findOne 以成功返回future 值,这意味着原始方法没有被调用,而原始方法又没有调用getOneById

    【讨论】:

    • getOnById 返回 Option,他正在尝试测试和模拟相同的类型,这就是它不起作用的原因
    • 也适用于以下情况:``` when(userRepo.findOne(name)).thenCallRealMethod() when(userRepo.getOneById(name)).thenThrow[RuntimeException] ```
    • 确实如此,尽管在很多方面模拟被测类型是错误的
    • @Bruno - 如果你能解释一下我的设计有什么问题,它将帮助我学习。 mocking type under test 是什么意思?
    • 测试的重点是确保类或特征(类型)的正确行为,为此,您向该类型的实例提供一些输入并确保有时会出现什么,您的类型依赖于其他组件,但您不想处理它们,因为您只对测试该代码单元(您的类型)感兴趣,所以这就是为什么 mocks 派上用场,您可以根据需要用 mocks 替换这些依赖项他们以某种方式表现,现在,如果你模拟你想要测试的类型,你实际上是在删除你想要测试的行为
    【解决方案2】:

    您不能模拟您要测试的类型,模拟没有该类型的原始行为。

    如果你只想存根被测类(或任何其他类型)的某些行为,你应该使用间谍,那么你只会这样做

    when(spyUserRepository.getOneById(userKeys)).thenThrow(classOf[RuntimeException])
    

    然后调用spyUserRepository.findOne(userKeys) 并断言它返回一个失败的未来

    也就是说,您的责任似乎有点混在一起,我建议您重新审视您的设计,因为不得不求助于间谍,因为这对我来说似乎是一种很大的代码气味。

    【讨论】:

      猜你喜欢
      • 2013-02-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-09
      • 2013-01-21
      • 1970-01-01
      相关资源
      最近更新 更多