Using ScalaTest with Play 2 Framework (Scala)

The default testing framework in Play 2 is specs2, however I prefer to use ScalaTest as I quite like it and I’m more familiar with it.

Setting up the sbt project and configuration to make it work, though, turned out to be unnecessarily tricky and cumbersome.

Here is eventually what I did to make it work (the full code is available on my github Sentinel repo).

nuke your ~/.ivy2 local repository

For reasons that largely escape me, specs2 got into my classpath and the Play default test runner was failing with weird ClassNotFound and MethodNotFound errors – it devolved into a full-scale wild goose chase, until I decided to `nuke` (aka, rm -rf) the local Ivy cache (typically, at ~/.ivy2 on a Linux system).

instructions on play users guide are lacking

Or, I should say, insufficient to get you going.

The actual build.sbt file that eventually got me going should look something like this:

libraryDependencies ++= Seq(
 "org.scalatest" %% "scalatest" % "2.2.1" % "test",
 "org.scalatestplus" %% "play" % "1.2.0" % "test",

Make sure you check out the Versions Versions Versions page on ScalaTestPlus and pick the right combination, given your favorite choice of Play version (I’m currently using 2.3.0).

get your imports right

This was also source of much unhappiness, as many matchers and DSL constructs were highlighted by IntelliJ as “syntax errors” and were not picked up by the auto-import feature:

import{OneAppPerSuite, PlaySpec}
import play.api.test.FakeRequest
import play.api.test.Helpers._

This has mostly to do with implicits – a Scala feature that I find disturbingly reminiscent of C++ implicit type conversions, which gave me so many hours of unbridled joy when dealing with STL and Boost (oh, those delightful compile error messages…)

fakerequest and friends

To test your API, you need to use FakeRequest; as I need the same headers defined for all calls, I find it useful to define a trait:

trait WithControllerAndRequest {
 val testController = new Controller with ApiController
 def fakeRequest(method: String = "GET", route: String = "/") = 
     FakeRequest(method, route)
             ("Date", "2014-10-05T22:00:00"),
             ("Authorization", "username=bob;hash=foobar==")

Generally, you then call your API methods using the apply() method:

 val apiResult = testController.orgById(orgId.toString).apply(fakeRequest())

However, if your API response contains a JSON body, the apply(request) will not return a Future[Result] but an Iteratee[Array[Byte], Result]: you should use call() instead:

 val apiResult = call(testController.modifyOrg(orgId.toString), request)

See my ApiControllerSpec class for a full set of examples.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: