Play Framework Custom Integration Test Configuration

I’ve been using the Play Framework 2.3 a lot recently for Java development, and have been finding it really easy to use. However, sometimes documentation is scarce, or doesn’t have an example of what it is I am looking for. In this blog post, I discuss how to create a custom testing configuration, and specify a custom path for your test source files.

The Problem: Integration Tests Too Slow

I was working on a web framework and got to the point where integration tests were taking more than a few seconds to run. This was expected, since I was performing end-to-end testing of several complex components. This isn’t a problem by itself, however, when performing test driven development I like to have the test runner executing in the background, picking up new tests and changes to existing tests as I write. For those of you who aren’t familiar with it, you can have the Typesafe Activator do this with the following command:

./activator ~test

The problem was that the default test runner was executing all of the tests, which was a pain since I would have to wait for all of the slow tests to run to see if there was a problem with any changes I made. What I needed was a way to separate out the unit tests from the more complicated integration tests.

The Start of a Solution

As it turns out, since the Activator uses sbt under the covers, it has access to a default integration testing configuration called IntegrationTest. All you need to do is change your root definition to include a configuration for IntegrationTest in build.sbt, and invoke the default integration test settings:

lazy val root = (project in file(".")).enablePlugins(PlayJava) configs(IntegrationTest)
 
Defaults.itSettings

This defines a new testing configuration called it. To run the integration tests all you would need to do is invoke:

it:test

While this put me on the right track, the problem is that the default IntegrationTest configuration defines the location of integration tests to be /src/it (you can see more about where these directories are by running show it:sourceDirectory and inspect it:sourceDirectory from an Activator prompt). Unfortunately, this layout doesn’t fit well with the rest of the Play Framework source tree. On top of that, when I moved my integration tests into the new directory layout, actually running the integration tests resulted in many hundreds of symbol errors such as:

[error]                 assertThat(contentAsString(result)).isEqualTo("mystring");
[error]                            ^
[error]   symbol: method contentAsString(Result)
[error] /projects/myproject/it/controllers/ControllerTest.java:120: error: cannot find symbol
[error]         running(fakeApplication(inMemoryDatabase()), new Runnable() {
[error]                                 ^
[error]   symbol:   method inMemoryDatabase()
[error] /projects/myproject/it/controllers/ControllerTest.java:98: error: cannot find symbol
[error]                         new FakeRequest(Helpers.POST, "/controller/create")
[error]                             ^
[error]   symbol: class FakeRequest
[error] /projects/myproject/it/controllers/ControllerTest.java:202: error: cannot find symbol

The problem is that the IntegrationTest configuration wasn’t pulling in the dependencies that were defined from the default Test configuration. That was a problem.

The Full Solution

Rather than play around with the IntegrationTest configuration, I figured it would be better to define my own custom configuration. The first step was to define my own configuration called ITest in the build.sbt file. I wanted this to be defined based off of the Test configuration, so I made my configuration extend it:

lazy val root = (project in file(".")).enablePlugins(PlayJava) configs(ITest) settings( inConfig(ITest)(Defaults.testSettings) : _*)
 
lazy val ITest = config("it") extend(Test)

So far so good. This created my new integration testing configuration called ITest. The config("it") portion of the second line defines how you can access that configuration from the Activator. I wanted to keep it simple, and just run it:test for my integration tests (like in the section above), so I kept it as "it".

The next hurdle was to define the proper directory layout. With a Play Framework 2.3.x application, the (partial) directory layout is as follows:

  • app is where all of your application source code (controllers, views, models, etc) goes.
  • test is where all of your testing source code goes.

What I wanted to do was create a new directory called it where all of the source code for integration tests would live. To do that, I added the following line to the build.sbt file:

sourceDirectory in ITest := baseDirectory.value / "/it"

This sort of worked. The problem was that it created other directories under /it for Java and Scala sources. Not quite what I wanted. So I defined three other source directories as follows:

javaSource in ITest := baseDirectory.value / "/it"
 
resourceDirectory in ITest := baseDirectory.value / "/it/resources"
 
scalaSource in ITest := baseDirectory.value / "/it"

This basically mashes both the Scala and Java sources into the /it directory, and names a specific directory for resources under /it/resources.

After syncing the changes to build.sbt, IntelliJ built the new directory structure for me. I moved my integration tests into the /it directory and ran the integration tests only:

[MyProject] $ it:test
[info] Updating {file:/projects/myproject/}root...
[info] Resolving jline#jline;2.11 ...
[info] Done updating.
[info] Passed: Total 57, Failed 0, Errors 0, Passed 57
[success] Total time: 49 s, completed 8-May-2015 1:40:58 AM

Just to be sure it would run the old unit tests (and only the unit tests – I knew there should only be 229 unit tests):

[MyProject] $ test
[info] Passed: Total 229, Failed 0, Errors 0, Passed 229
[success] Total time: 18 s, completed 8-May-2015 1:42:35 AM

Perfect.

Wrapping Up

To create a custom integration testing configuration, I only had to change a few lines in the build.sbt file. All together:

lazy val root = (project in file(".")).enablePlugins(PlayJava) configs(ITest) settings( inConfig(ITest)(Defaults.testSettings) : _*)
 
lazy val ITest = config("it") extend(Test)
 
sourceDirectory in ITest := baseDirectory.value / "/it"
 
javaSource in ITest := baseDirectory.value / "/it"
 
resourceDirectory in ITest := baseDirectory.value / "/it/resources"
 
scalaSource in ITest := baseDirectory.value / "/it"

Now, running integration tests is as easy as it:test while running unit tests remains as test.