How to test methods that return Future?

前端 未结 6 2124
时光说笑
时光说笑 2021-02-07 08:25

I\'d like to test a method that returns a Future. My attempts were as follows:

import  org.specs2.mutable.Specification
import scala.concurrent.Exec         


        
相关标签:
6条回答
  • 2021-02-07 08:33

    Wondering why @etorreborre did not mention "eventually"

    See https://github.com/etorreborre/specs2/blob/master/tests/src/test/scala/org/specs2/matcher/EventuallyMatchersSpec.scala#L10-L43

    class EventuallyMatchersSpec extends Specification with FutureMatchers with ExpectationsDescription { section("travis")
    addParagraph { """
    `eventually` can be used to retry any matcher until a maximum number of times is reached
    or until it succeeds.
    """ }
    
      "A matcher can match right away with eventually" in {
        1 must eventually(be_==(1))
      }
      "A matcher can match right away with eventually, even if negated" in {
        "1" must not (beNull.eventually)
      }
      "A matcher will be retried automatically until it matches" in {
        val iterator = List(1, 2, 3).iterator
        iterator.next must be_==(3).eventually
      }
      "A matcher can work with eventually and be_== but a type annotation is necessary or a be_=== matcher" in {
        val option: Option[Int] = Some(3)
        option must be_==(Some(3)).eventually
      }
    
    0 讨论(0)
  • 2021-02-07 08:43

    onComplete returns Unit, so that block of code returns immediately and the test ends before being able to do anything. In order to properly test the result of a Future, you need to block until it completes. You can do so using Await, and setting a maximum Duration to wait.

    import scala.concurrent._
    import scala.concurrent.duration._
    
    Await.result(testImage, Duration("10 seconds")) must not have length(0)
    
    0 讨论(0)
  • 2021-02-07 08:45

    You can use the Matcher.await method to transform a Matcher[T] into a Matcher[Future[T]]:

    val testImage: Future[String] =
       AsyncWebClient.get("https://www.google.cz/images/srpr/logo11ww.png")  
    
    // you must specify size[String] here to help type inference
    testImage must not have size[String](0).await
    
    // you can also specify a number of retries and duration between retries
    testImage must not have size[String](0).await(retries = 2, timeout = 2.seconds)
    
    // you might also want to check exceptions in case of a failure
    testImage must throwAn[Exception].await
    
    0 讨论(0)
  • 2021-02-07 08:47

    There is a nice thing for that in specs2 - implicit await method for Future[Result]. If you take advantage of future transformations you can write like this:

    "save notification" in {
      notificationDao.saveNotification(notification) map { writeResult =>
        writeResult.ok must be equalTo (true)
      } await
    }
    

    Future composition comes to the rescue when some data arrangement with async functions is needed:

    "get user notifications" in {
      {
        for {
          _ <- notificationDao.saveNotifications(user1Notifications)
          _ <- notificationDao.saveNotifications(user2Notifications)
          foundUser1Notifications <- notificationDao.getNotifications(user1)
        } yield {
          foundUser1Notifications must be equalTo (user1Notifications)
        }
      } await
    }
    

    Note how we have to use an additional block around for-comprehension to convince compiler. I think it's noisy, so if we turn await method in a function we come up with a nicer syntax:

    def awaiting[T]: Future[MatchResult[T]] => Result = { _.await }
    
    "get user notifications" in awaiting {
      for {
        _ <- notificationDao.saveNotifications(user1Notifications)
        _ <- notificationDao.saveNotifications(user2Notifications)
        foundUser1Notifications <- notificationDao.getNotifications(user1)
      } yield {
        foundUser1Notifications must be equalTo (user1Notifications)
      }
    }
    
    0 讨论(0)
  • 2021-02-07 08:49

    Took me awhile to find this so thought I'd share. I should've read the release notes. In specs2 v3.5, it is required to use implicit ExecutionEnv to use await for future. This can also be used for future transformation (i.e. map) see http://notes.implicit.ly/post/116619383574/specs2-3-5.

    excerpt from there for quick reference:

    import org.specs2.concurrent.ExecutionEnv
    
    class MySpec extends mutable.Specification {
      "test of a Scala Future" >> { implicit ee: ExecutionEnv =>
        Future(1) must be_>(0).await
      }
    }
    
    0 讨论(0)
  • 2021-02-07 08:53

    Await is an anti pattern. Shouldn't ever use it. You can use traits like ScalaFutures, IntegrationPatience, and Eventually.

    Example:

    import org.specs2.mutable.Specification
    import scala.concurrent.ExecutionContext.Implicits.global
    import scala.util.{Failure, Success}
    import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures}
    import scala.concurrent.Future
    
    class AsyncWebClientSpec extends Specification with ScalaFutures
        with IntegrationPatience{
    
        "WebClient when downloading images" should {
            "for a valid link return non-zero content " in {
                whenReady(Future.successful("Done")){ testImage =>
                    testImage must be equalTo "Done"           
                    // Do whatever you need
                }
    
            }
        }
    }
    
    0 讨论(0)
提交回复
热议问题