17

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?

Jacek Laskowski
  • 64,943
  • 20
  • 207
  • 364
jaksky
  • 3,073
  • 3
  • 31
  • 65

6 Answers6

20

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
Eric
  • 15,249
  • 36
  • 60
  • 6
    Do I need any special imports to make it work? I am using specs2 3.6.2, and I am getting the following error: `value await is not a member of org.specs2.matcher.BeEqualTo` `[error] Future(1) must beEqualTo(1).await` – Yar Dec 15 '15 at 09:01
  • 3
    Yes you need an implicit [execution environment](https://etorreborre.github.io/specs2/guide/SPECS2-3.6.6/org.specs2.guide.ExecutionEnvironments.html#implicit-executionenv) – Eric Dec 15 '15 at 11:37
14

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
  }
}
subandroid
  • 211
  • 3
  • 8
  • 1
    You can also add it to the class signature `case class MyFutureSpec(implicit ee: ExecutionEnv) extends Specification` – nmat Nov 24 '16 at 15:32
7

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)
  }
}
Mikhail Golubtsov
  • 5,247
  • 2
  • 27
  • 34
  • Could you add the complete snippet using it in a complete test class please? I got "await" not available. Tried to fix it, but damn the implicit imports. Hard to get it right. – James Jul 18 '17 at 08:59
4

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
  }
SemanticBeeng
  • 823
  • 1
  • 9
  • 14
  • see also https://github.com/etorreborre/specs2/blob/master/tests/src/test/scala/org/specs2/matcher/FutureMatchersSpec.scala#L14-L16 – SemanticBeeng Mar 21 '15 at 14:25
  • 3
    that's because eventually just retries a value `=>T` which might change when re-invoked (e.g. the `iterator.next` example), and not a `Future` which you have to wait for. – Eric Mar 26 '15 at 00:00
4

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
            }

        }
    }
}
epinal
  • 1,063
  • 10
  • 24
3

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)
Michael Zajac
  • 53,182
  • 7
  • 105
  • 130
  • That I was considering as well, but what wasn't clear to me was how do I test Failure scenario since Await. Result returns T – jaksky Jan 03 '15 at 07:10
  • Though I don't think its a good idea to put time constraint for assertion, still it works :) – prayagupd Nov 24 '16 at 01:30