jaksky
jaksky

Reputation: 3405

How to test methods that return Future?

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

import  org.specs2.mutable.Specification
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}

class AsyncWebClientSpec extends Specification{

  "WebClient when downloading images" should {
    "for a valid link return non-zero content " in {
      val testImage = AsyncWebClient.get("https://www.google.cz/images/srpr/logo11ww.png")
      testImage.onComplete { res => 
        res match {
          case Success(image) => image must not have length(0)
          case _ =>
        }
        AsyncWebClient.shutDown
      }
    }
  }
}

Apart from the fact that I am unable to make this code work I guess that there could be a better way of testing a futures with a Future-oriented matcher.

How to do it properly in specs2?

Upvotes: 21

Views: 21813

Answers (6)

epinal
epinal

Reputation: 1465

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

whenReady does the magic you are looking for.

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
            }

        }
    }
}

Upvotes: 11

subandroid
subandroid

Reputation: 211

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
  }
}

Upvotes: 14

SemanticBeeng
SemanticBeeng

Reputation: 977

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
  }

Upvotes: 4

Mikhail Golubtsov
Mikhail Golubtsov

Reputation: 6653

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)
  }
}

Upvotes: 7

Michael Zajac
Michael Zajac

Reputation: 55569

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)

Upvotes: 3

Eric
Eric

Reputation: 15557

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

Upvotes: 21

Related Questions