Как проверить методы, которые возвращают будущее?
Я бы хотел протестировать метод, который возвращает 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?
Ответы
Ответ 1
Вы можете использовать метод 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
Ответ 2
Взял меня немного, чтобы найти это так, что я бы поделился. Я должен был прочитать заметки о выпуске. В specs2 v3.5 требуется использовать неявный ExecutionEnv для использования в ожидании будущего. Это также можно использовать для будущего преобразования (например, карты), см. 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
}
}
Ответ 3
Для этого есть приятная вещь в 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)
}
}
Ответ 4
onComplete
возвращает Unit
, так что блок кода возвращается немедленно, и тест заканчивается, прежде чем он сможет что-либо сделать. Чтобы правильно проверить результат Future
, вам нужно заблокировать его до завершения. Вы можете сделать это с помощью Await
и установить максимальный Duration
для ожидания.
import scala.concurrent._
import scala.concurrent.duration._
Await.result(testImage, Duration("10 seconds")) must not have length(0)
Ответ 5
Интересно, почему @etorreborre не упоминал "в конце концов"
См. 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
}