Как проверить методы, которые возвращают будущее?

Я хотел бы проверить метод, который возвращает Future, Мои попытки были следующими:

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

Помимо того, что я не могу заставить этот код работать, я думаю, что мог бы быть лучший способ тестирования фьючерса с Future-ориентированный мэтчер.

Как правильно сделать это в specs2?

6 ответов

Решение

Вы можете использовать Matcher.await метод для преобразования Matcher[T] в 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

Мне потребовалось некоторое время, чтобы найти это, так что я поделился. Я должен был прочитать примечания к выпуску. В specs2 v3.5 требуется использовать неявный ExecutionEnv, чтобы использовать await на будущее. Это также может быть использовано для будущего преобразования (т. Е. Карты), см. http://notes.implicit.ly/post/116619383574/specs2-3-5.

отрывок оттуда для быстрого ознакомления:

import org.specs2.concurrent.ExecutionEnv

class MySpec extends mutable.Specification {
  "test of a Scala Future" >> { implicit ee: ExecutionEnv =>
    Future(1) must be_>(0).await
  }
}

Жду анти-паттерн. Никогда не должен использовать это. Вы можете использовать такие черты, как ScalaFutures, IntegrationPatience, а также Eventually,

Пример:

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
            }

        }
    }
}

В specs2 есть хорошая вещь - неявная await метод для Future[Result], Если вы используете преимущества будущих преобразований, вы можете написать так:

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

Будущая композиция приходит на помощь, когда требуется некоторая компоновка данных с асинхронными функциями:

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

Обратите внимание, как мы должны использовать дополнительный блок для понимания, чтобы убедить компилятор. Я думаю, что это шумно, поэтому, если мы переходим await метод в функции, мы придумали более приятный синтаксис:

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

Интересно, почему @etorreborre не упомянул "в конце концов"

См. https://github.com/etorreborre/specs2/blob/master/tests/src/test/scala/org/specs2/matcher/EventuallyMatchersSpec.scala

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
  }

onComplete возвращается Unit, так что блок кода немедленно возвращается, и тест завершается, прежде чем можно будет что-либо делать. Для того, чтобы правильно проверить результат Future, вам нужно заблокировать, пока он не завершится. Вы можете сделать это, используя Awaitи установив максимум Duration ждать.

import scala.concurrent._
import scala.concurrent.duration._

Await.result(testImage, Duration("10 seconds")) must not have length(0)
Другие вопросы по тегам