Advertisement
mitrakov

Test Futures

Sep 25th, 2018
516
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Scala 5.86 KB | None | 0 0
  1. // Testing Futures with ScalaTest.
  2.  
  3. // MyController.scala:
  4. import javax.inject.{Inject, Singleton}
  5. import scala.concurrent.{ExecutionContext, Future}
  6. import play.api.Logger
  7. import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents}
  8.  
  9. @Singleton
  10. class MyController @Inject()(
  11.     cc: ControllerComponents
  12.   )(implicit ec: ExecutionContext) extends AbstractController(cc) {
  13.  
  14.   protected def resultFuture: Future[Int] = Future {
  15.     Logger.info("Future started")
  16.     Thread.sleep(3000)
  17.     Logger.info("Future finished")
  18.     5
  19.   }
  20.  
  21.   def getFive: Action[AnyContent] = Action.async {
  22.     resultFuture map { result =>
  23.       Ok(s"Result is $result")
  24.     } recover { case e =>
  25.       InternalServerError(s"Error: $e")
  26.     }
  27.   }
  28. }
  29.  
  30. // TrixSpec.scala:
  31. import scala.concurrent.{Await, ExecutionContext, Future}
  32. import scala.concurrent.duration._
  33. import scala.language.postfixOps
  34.  
  35. import org.scalatest.mockito.MockitoSugar
  36. import org.scalatestplus.play.PlaySpec
  37. import org.scalatest.concurrent.ScalaFutures
  38. import org.scalatest.concurrent.ScalaFutures._
  39. import play.api.mvc.{ControllerComponents, Result}
  40. import play.api.test.Helpers._
  41. import play.api.test.{FakeRequest, Helpers}
  42.  
  43. import controllers.MyController
  44.  
  45. class TrixSpec extends PlaySpec with MockitoSugar {
  46.  
  47.   private val cc: ControllerComponents = Helpers.stubControllerComponents()
  48.   implicit val ec: ExecutionContext = cc.executionContext
  49.  
  50.   private val controller = new MyController(cc)
  51.  
  52.   "A MyController" when {
  53.     "getFive is called" should {
  54.       "return 5" in {
  55.         val result: Future[Result] = controller.getFive.apply(FakeRequest()) // note that Future[Result] is returned
  56.         result foreach { res =>
  57.           res.header.status mustBe OK
  58.         }
  59.       }
  60.     }
  61.   }
  62. }
  63.  
  64. // testing: sbt "testOnly *TrixSpec"
  65. [info] TrixSpec:
  66. [info] A MyController
  67. [info]   when getFive is called
  68. [info] application - Future started
  69. [info]   - should return 5
  70. [info] ScalaTest
  71. [info] Run completed in 1 second, 598 milliseconds.
  72. [info] Total number of tests run: 1
  73. [info] Suites: completed 1, aborted 0
  74. [info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
  75. [info] All tests passed.
  76.  
  77. // Great! Let's make mutation test:
  78. res.header.status mustBe BAD_REQUEST
  79.  
  80. [info] TrixSpec:
  81. [info] A MyController
  82. [info]   when getFive is called
  83. [info] application - Future started
  84. [info]   - should return 5
  85. [info] ScalaTest
  86. [info] Run completed in 1 second, 616 milliseconds.
  87. [info] Total number of tests run: 1
  88. [info] Suites: completed 1, aborted 0
  89. [info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
  90. [info] All tests passed.
  91.  
  92. // WTF? This is still passed!
  93. // The problem is that code is asynchronous and "mustBe" validation is actually performed, but AFTER test case is processed.
  94.  
  95. // Solution 1: Await (not recommended):
  96. val res = Await.result(result, 5 seconds)
  97. res.header.status mustBe BAD_REQUEST
  98.  
  99. [info] TrixSpec:
  100. [info] A MyController
  101. [info]   when getFive is called
  102. [info] application - Future started
  103. [info] application - Future finished
  104. [info]   - should return 5 *** FAILED ***
  105. [info]     200 was not equal to 400 (TrixSpec.scala:24)
  106. [info] ScalaTest
  107. [info] Run completed in 4 seconds, 705 milliseconds.
  108. [info] Total number of tests run: 1
  109. [info] Suites: completed 1, aborted 0
  110. [info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
  111. [info] *** 1 TEST FAILED ***
  112.  
  113. // Please refer to this doc to learn why it is not recommended:
  114. // https://docs.scala-lang.org/overviews/core/futures.html#introduction
  115.  
  116. // Solution 2: Helpers:
  117. Helpers.status(result) mustBe BAD_REQUEST
  118. Helpers.contentAsString(result) must endWith("5")
  119.  
  120. [info] TrixSpec:
  121. [info] A MyController
  122. [info]   when getFive is called
  123. [info] application - Future started
  124. [info] application - Future finished
  125. [info]   - should return 5 *** FAILED ***
  126. [info]     200 was not equal to 400 (TrixSpec.scala:23)
  127. [info] ScalaTest
  128. [info] Run completed in 4 seconds, 730 milliseconds.
  129. [info] Total number of tests run: 1
  130. [info] Suites: completed 1, aborted 0
  131. [info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
  132. [info] *** 1 TEST FAILED ***
  133.  
  134. // Solution 3: ScalaFutures:
  135. ScalaFutures.whenReady(result, timeout(5 seconds)) { res => // timeout is optional (default is 150 ms)
  136.   res.header.status mustBe BAD_REQUEST
  137. }
  138.  
  139. [info] TrixSpec:
  140. [info] A MyController
  141. [info]   when getFive is called
  142. [info] application - Future started
  143. [info] application - Future finished
  144. [info]   - should return 5 *** FAILED ***
  145. [info]     200 was not equal to 400 (TrixSpec.scala:27)
  146. [info] ScalaTest
  147. [info] Run completed in 4 seconds, 708 milliseconds.
  148. [info] Total number of tests run: 1
  149. [info] Suites: completed 1, aborted 0
  150. [info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
  151. [info] *** 1 TEST FAILED ***
  152.  
  153. // Solution 3a: ScalaFutures DSL:
  154. // add this line to a class scope if you need timeouts > than 150 ms
  155. implicit val patienceConfig: PatienceConfig = PatienceConfig(timeout = 5 seconds, interval = 100 milliseconds)
  156. ...
  157. result.futureValue.header.status mustBe BAD_REQUEST
  158.  
  159. [info] TrixSpec:
  160. [info] A MyController
  161. [info]   when getFive is called
  162. [info] application - Future started
  163. [info] application - Future finished
  164. [info]   - should return 5 *** FAILED ***
  165. [info]     200 was not equal to 400 (TrixSpec.scala:29)
  166. [info] ScalaTest
  167. [info] Run completed in 4 seconds, 781 milliseconds.
  168. [info] Total number of tests run: 1
  169. [info] Suites: completed 1, aborted 0
  170. [info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
  171. [info] *** 1 TEST FAILED ***
  172.  
  173. // other useful examples:
  174. result.isReadyWithin(5 seconds)                                    // checks time bounds
  175. ScalaFutures.whenReady(result) { _ mustBe a [SQLException]}        // checks failure
  176. assert(result.failed.futureValue.isInstanceOf[RuntimeException])   // checks failure
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement