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.

      Leave a Reply

      Your email address will not be published.


      *