2
votes

Using Scala play version 2.5 and trying to follow the guidelines for unit testing controllers as per documentation at: https://www.playframework.com/documentation/2.5.x/ScalaTestingWithScalaTest

there's no example for unit testing an async controller.

I'm trying to create a unit test for my controller which has an async action method, I ended up mocking some objects

class ProductController @Inject()(
    action: ProductAction,
    handler: ProductResourceHandler)(implicit ec: ExecutionContext)
    extends Controller {

  /**
   * Fetch a list of products
   */
  def index: Action[AnyContent] = {
    action.async { implicit request =>
      handler.find.map { list =>                                
        Ok(Json.toJson(list))
      }
    }
  }
// ...
}

My unit test:

import scala.concurrent.Future
import org.scalatestplus.play._
import play.api.mvc._
import play.api.test._
import play.api.test.Helpers._
import org.scalatest.mockito.MockitoSugar
import product.ProductAction
import product.ProductController
import product.services.maps.GeolocationService
import product.ProductResourceHandler
import play.api.libs.concurrent.Execution.Implicits._
import scala.io.Source
import play.api.libs.json.Json
import product.model.OfferList
import product.model.OfferDetail
import org.mockito.Mockito._

class ProductControllerSpec extends PlaySpec with Results with MockitoSugar {

  private val productList = Json.parse(Source.fromFile("conf/app/sample_products.json").getLines.mkString).as[ProductList]

  "Example Page#index" should {
    "should be valid" in {
      val action = new ProductAction()
      val handler = mock[ProductResourceHandler]      
      when(handler.find) thenReturn Future.successful(productList)      
      val controller = new ProductController(action, handler)
      val result: Future[Result] = controller.index().apply(FakeRequest())      
      val bodyText: String = contentAsString(result)                                
      bodyText != null mustBe true
    }
  }
}

up till now it's working but I'm wondering if this follows the best practices or guidelines for this type of test. Is this the right way to unit test an async controller in Scala play framework?

2

2 Answers

2
votes

Some of my suggestions are

  • use contentAsJson instead of contentAsString and inspect the returned json.
  • use route to directly invoke the controller and test response (for eg route(app, FakeRequest..)
  • use status method to check if the returned status is HTTP OK (status code 200)

    val Some(result) = route(app, FakeRequest(GET, 
                              controllers.routes. ProductController.index.path()))
    status(result) must be (OK)
    val json = contentAsJson(result)
    // inspect json fields like if you have to check if the json 
    // has string field called id you can do (json \ "id").as[String] must be ("<id value>")
    
1
votes

According to Play documentation:

Play actions are asynchronous by default.

This means even if you are not using Action.async { Future { myAnonymousFunction } } but just Action { myAnonymousFunction }, internally the result of myAnonymousFunction will be enclosed in a Future.

For instance, say you have

class HelloWorld extends Controller {
  def index = Action { request => Ok("") }
}

then

(new HelloWorld).index().apply(FakeRequest())

still has type

Future[Result]

This leads me to believe that your unit test is indeed appropriate way of testing controllers, that is, Play's documentation is implicitly covering also the case of Action.async.