Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Testing Futures with ScalaTest.
- // MyController.scala:
- import javax.inject.{Inject, Singleton}
- import scala.concurrent.{ExecutionContext, Future}
- import play.api.Logger
- import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents}
- @Singleton
- class MyController @Inject()(
- cc: ControllerComponents
- )(implicit ec: ExecutionContext) extends AbstractController(cc) {
- protected def resultFuture: Future[Int] = Future {
- Logger.info("Future started")
- Thread.sleep(3000)
- Logger.info("Future finished")
- 5
- }
- def getFive: Action[AnyContent] = Action.async {
- resultFuture map { result =>
- Ok(s"Result is $result")
- } recover { case e =>
- InternalServerError(s"Error: $e")
- }
- }
- }
- // TrixSpec.scala:
- import scala.concurrent.{Await, ExecutionContext, Future}
- import scala.concurrent.duration._
- import scala.language.postfixOps
- import org.scalatest.mockito.MockitoSugar
- import org.scalatestplus.play.PlaySpec
- import org.scalatest.concurrent.ScalaFutures
- import org.scalatest.concurrent.ScalaFutures._
- import play.api.mvc.{ControllerComponents, Result}
- import play.api.test.Helpers._
- import play.api.test.{FakeRequest, Helpers}
- import controllers.MyController
- class TrixSpec extends PlaySpec with MockitoSugar {
- private val cc: ControllerComponents = Helpers.stubControllerComponents()
- implicit val ec: ExecutionContext = cc.executionContext
- private val controller = new MyController(cc)
- "A MyController" when {
- "getFive is called" should {
- "return 5" in {
- val result: Future[Result] = controller.getFive.apply(FakeRequest()) // note that Future[Result] is returned
- result foreach { res =>
- res.header.status mustBe OK
- }
- }
- }
- }
- }
- // testing: sbt "testOnly *TrixSpec"
- [info] TrixSpec:
- [info] A MyController
- [info] when getFive is called
- [info] application - Future started
- [info] - should return 5
- [info] ScalaTest
- [info] Run completed in 1 second, 598 milliseconds.
- [info] Total number of tests run: 1
- [info] Suites: completed 1, aborted 0
- [info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
- [info] All tests passed.
- // Great! Let's make mutation test:
- res.header.status mustBe BAD_REQUEST
- [info] TrixSpec:
- [info] A MyController
- [info] when getFive is called
- [info] application - Future started
- [info] - should return 5
- [info] ScalaTest
- [info] Run completed in 1 second, 616 milliseconds.
- [info] Total number of tests run: 1
- [info] Suites: completed 1, aborted 0
- [info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
- [info] All tests passed.
- // WTF? This is still passed!
- // The problem is that code is asynchronous and "mustBe" validation is actually performed, but AFTER test case is processed.
- // Solution 1: Await (not recommended):
- val res = Await.result(result, 5 seconds)
- res.header.status mustBe BAD_REQUEST
- [info] TrixSpec:
- [info] A MyController
- [info] when getFive is called
- [info] application - Future started
- [info] application - Future finished
- [info] - should return 5 *** FAILED ***
- [info] 200 was not equal to 400 (TrixSpec.scala:24)
- [info] ScalaTest
- [info] Run completed in 4 seconds, 705 milliseconds.
- [info] Total number of tests run: 1
- [info] Suites: completed 1, aborted 0
- [info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
- [info] *** 1 TEST FAILED ***
- // Please refer to this doc to learn why it is not recommended:
- // https://docs.scala-lang.org/overviews/core/futures.html#introduction
- // Solution 2: Helpers:
- Helpers.status(result) mustBe BAD_REQUEST
- Helpers.contentAsString(result) must endWith("5")
- [info] TrixSpec:
- [info] A MyController
- [info] when getFive is called
- [info] application - Future started
- [info] application - Future finished
- [info] - should return 5 *** FAILED ***
- [info] 200 was not equal to 400 (TrixSpec.scala:23)
- [info] ScalaTest
- [info] Run completed in 4 seconds, 730 milliseconds.
- [info] Total number of tests run: 1
- [info] Suites: completed 1, aborted 0
- [info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
- [info] *** 1 TEST FAILED ***
- // Solution 3: ScalaFutures:
- ScalaFutures.whenReady(result, timeout(5 seconds)) { res => // timeout is optional (default is 150 ms)
- res.header.status mustBe BAD_REQUEST
- }
- [info] TrixSpec:
- [info] A MyController
- [info] when getFive is called
- [info] application - Future started
- [info] application - Future finished
- [info] - should return 5 *** FAILED ***
- [info] 200 was not equal to 400 (TrixSpec.scala:27)
- [info] ScalaTest
- [info] Run completed in 4 seconds, 708 milliseconds.
- [info] Total number of tests run: 1
- [info] Suites: completed 1, aborted 0
- [info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
- [info] *** 1 TEST FAILED ***
- // Solution 3a: ScalaFutures DSL:
- // add this line to a class scope if you need timeouts > than 150 ms
- implicit val patienceConfig: PatienceConfig = PatienceConfig(timeout = 5 seconds, interval = 100 milliseconds)
- ...
- result.futureValue.header.status mustBe BAD_REQUEST
- [info] TrixSpec:
- [info] A MyController
- [info] when getFive is called
- [info] application - Future started
- [info] application - Future finished
- [info] - should return 5 *** FAILED ***
- [info] 200 was not equal to 400 (TrixSpec.scala:29)
- [info] ScalaTest
- [info] Run completed in 4 seconds, 781 milliseconds.
- [info] Total number of tests run: 1
- [info] Suites: completed 1, aborted 0
- [info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0
- [info] *** 1 TEST FAILED ***
- // other useful examples:
- result.isReadyWithin(5 seconds) // checks time bounds
- ScalaFutures.whenReady(result) { _ mustBe a [SQLException]} // checks failure
- assert(result.failed.futureValue.isInstanceOf[RuntimeException]) // checks failure
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement