How to execute tests on the argument that a controller passes to the view in Play Framework

StackOverflow https://stackoverflow.com//questions/20005032

  •  20-12-2019
  •  | 
  •  

Question

In our play application every controller function fetches data from the database (or some other way) and passes these values to the result

def index = Action { implicit request =>
    val newsItems: List[String] = fetchNewsFromDB()
    Ok(views.html.home.index(newsItems))
  }

def fetchNewsFromDB() = List("Headline1", "Headline2")

I am writing tests using specifiactions (based on the documentation http://www.playframework.com/documentation/2.2.x/ScalaTest)

According to this documentation by controller as follows. In the next test I want to make sure that the index page contains a headline. I do this by checking if there exists a div with the class "headline"

"Example Page#index" should {
    "should contain a headline" in {
      val controller = new TestController()
      val result: Future[SimpleResult] = controller.index().apply(FakeRequest())
      val bodyText: String = contentAsString(result)
      bodyText.toLowerCase must contain("<div class=\"headline\"")
    }
  }

However I would rather check whether the list newsItems which the controller passes to the view is nonempty.

What is the best way to do this? Is it possible to this in a generic way for which little modification of the controllers is required?

Was it helpful?

Solution

I too was frustrated that I couldn't intercept the parameters on their way to the template - and in fact it can become extremely difficult to even get the template to render at all in tests if you have a lot of "state" in your pages (for example, implicits that provide the user object, navigation helpers etc).

What I ended up doing was putting in an extra "seam" for testability in my controllers; in my tests, I extend the controller under test, replacing the HTML rendering function with a mocked one, which I can then use to verify the parameters.

Here's a simple example based on your "news" Action; first, the controller, which is no longer an object so we can extend it:

object Application extends ApplicationController

trait ApplicationController extends Controller {

  def newsAction = Action {
    Ok(renderNews("this is the news"))
  }

  def renderNews(s:List[String]):Html = html.sandbox(s)
}

The renderNews method gives us the all-important "test seam". I think it also actually improves the readability of controller methods too, which is nice :-)

Now, the unit test:

class ApplicationSpec extends Specification with Mockito {

  val mockedNewsRenderer = mock[List[String] => Html]

  val controller = new ApplicationController {
    override def renderNews(s:List[String]) = mockedNewsRenderer(s)
  }

  "Application News Controller" should {

    "Pass a non-empty list of news items to the template" in {
      val result = controller.newsAction(FakeRequest())

      status(result) must beEqualTo(200)

      val captor = ArgumentCaptor.forClass(classOf[List[String]])

      there was one(mockedNewsRenderer).apply(captor.capture())
      val theArgument = captor.getValue
      theArgument.isEmpty must beFalse
    }
  }
}

We create a mock to stand-in for the renderNews function, extend the controller so that we can substitute it in (note that we don't change anything else about it of course), and then call the action as normal. Note that we still get a standard Play Result so we can still check status codes etc, but then, we can use the Mockito verify functionality that's built into Specs2, together with Mockito's ArgumentCaptor facility to assert that our template was indeed called, and that it was supplied with a non-empty list of strings.

This approach has worked well for me - it makes it possible to get really good code coverage of your controllers with fast-running and easy-to-write unit tests.

OTHER TIPS

You have a very good question and a very valid point on testing controllers, but I'm afraid it can't be done easily. The problem is that the views compile to Scala functions meaning when you call views.html.home.index(newsItems) it will return an object of Html, which already has the Html put together and compiled. If you would like to test what get's passed in you need to intercept it before the view is called.

To solve this you would have to rewrite your controllers, by moving all your business logic out of the controller and only have the necessary request handling code there. That would almost be easier to test.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top