poornerd

my thoughts on programming and other nerdy stuff

How to integrate Concordion in Play Framework and write acceptance tests in natural language

| 4 Comments

Screen Shot 2013-07-05 at 10.24.25After start­ing to read “Spec­i­fi­ca­tion by Exam­ple”, I wanted to have a go a writ­ing accep­tance tests for my Play Frame­work projects.  I stum­bled onto Con­cor­dion (http://​www​.con​cor​dion​.org/), because it is sim­i­lar to Cucum­ber but writ­ten in and for Java.

Con­cor­dion is sim­i­lar to Cucum­ber but focuses on readability.

Con­cor­dion inte­grates as Unit tests (which is the pri­mary focus of this post), and lets you write the Tests in Nat­ural Lan­guage in HTML Files so that it also becomes self documenting.

I got most of how to inte­grate Con­cor­dion from Craig Aspinall in Aus­tralia (@aspinall) who had writ­ten a Blog post about it, but which was no long “online”.   (he sent me a link to the mark­down so I could try it out — thank Craig!)

Inte­grat­ing Concordion

You first need to add the depen­dency to your Build.scala, and then add a line so that the SBT copies the Con­cor­dion HTML files:

import sbt._
import Keys._
import play.Project._

object ApplicationBuild extends Build {

    val appName         = "computer-database"
    val appVersion      = "1.0"

    val appDependencies = Seq(
       "org.concordion" % "concordion" % "1.4.2" % "test"  ,
      javaCore,
      javaJdbc,
      javaEbean
    )

    val main = play.Project(appName, appVersion, appDependencies).settings(
      // Next, you need to instruct SBT to copy the Concordion HTML files to the target folder
        unmanagedClasspath in Test <+= (baseDirectory) map { bd => Attributed.blank(bd / "test") }
    )
}

Telling Play where to out­put the Test results to

This turned out to be a chal­lenge. If you do not set the concordion.output.dir sys­tem prop­erty, then they are out­puted to your Java temp direc­tory. Craig sug­gested set­ting it in the Build.scala, but as it turns out the most recent Ver­sion of Play forks the Tests into a sep­a­rate process and loses the set­ting. So I ended up adding this to my Global Set­tings class:

import play.Application;
import play.GlobalSettings;
public class Global extends GlobalSettings {

    public void onStart(play.Application arg0) {
        if (arg0.isTest())
            System.setProperty("concordion.output.dir", "target/test-reports/concordion");
    }

}

Note: I think I dis­cov­ered another Bug in Play, because although this works, if I run “test-only” it doesn’t!!!

The Test

The key to get­ting Play to rec­og­nize and run Unit Tests is the @Test anno­ta­tion, so it seems that the eas­i­est way to get Play to run the Con­cor­dion tests is to add a method like this to your Con­cor­dion fix­ture class:

    @Test
    public void runThisTest() {
    }

I ended up imple­ment­ing one of the tuto­r­ial exam­ples from here: http://​www​.con​cor​dion​.org/​T​u​t​o​r​i​a​l​.​h​tml

You write the test case in HTML, and use <span> tags with Con­cor­dion ele­ments to spec­ify the paramters and assertions:

<html xmlns:concordion="http://www.concordion.org/2007/concordion">

    <head>
        <link href="../concordion.css" rel="stylesheet" type="text/css" />
    </head>

    <body>

        <h1>Splitting Names</h1>

        <p>
            To help personalise our mailshots we want to have the first name
            and last name of the customer. Unfortunately the customer data
            that we are supplied only contains full names.
        </p>

        <p>
            The system therefore attempts to break a supplied full name into
            its constituents by splitting around whitespace.
        </p>

        <div class="example">

            <h3>Example</h3>

            <p>
                The full name
                <span concordion:execute="#result = split(#TEXT)">John Smith</span>
                will be broken into first name
                <span concordion:assertEquals="#result.firstName">John</span>
                and last name
                <span concordion:assertEquals="#result.lastName">Smith</span>.
            </p>

        </div>
    </body>
</html>

Then you write a fix­ture class with the Tests (and the @Test “hack” I mentioned).

import org.junit.Test;
import play.mvc.Result;

import java.util.HashMap;
import java.util.Map;

import static org.fest.assertions.Assertions.assertThat;
import static play.test.Helpers.*;

import org.concordion.integration.junit4.ConcordionRunner;
import org.junit.runner.RunWith;
import org.junit.Test;

@RunWith(ConcordionRunner.class)
public class ConcordianTestFixture {

    public Result split(String fullName) {
        Result result = new Result();
        String[] words = fullName.split(" ");
        result.firstName = words[0];
        result.lastName = words[1];
        return result;
    }

    class Result {
        public String firstName;
        public String lastName;
    }

    @Test
    public void runThisTest() {
    }
}

When you run the tests, then the result­ing HTML page it out­puted (as pre­vi­ously defined) to your target/test-reports directory. Here is an exam­ple of a failed test (for a suc­cess­ful one, see the image at the start of the post):

Screen Shot 2013-07-05 at 10.30.39

I hope you try this out!  Let me know if you have more ideas or tips…

If you have read this far, you may as well fol­low me on Twit­ter:

Author: poornerd

Tech­nol­o­gist, Entre­pre­neur, Vision­ary, Pro­gram­mer :: Grad­u­ated from USC (Uni­ver­sity of South­ern Cal­i­for­nia) with a degree in Com­puter Sci­ence. After 10+ years of free­lance con­sult­ing and pro­gram­ming, he co-founded Site­Force AG eBusi­ness Solu­tions in 1999 in Munich (München), Ger­many.

4 Comments

  1. Note: I think I dis­cov­ered another Bug in Play, because although this works, if I run “test-only” it doesn’t!!!”

    If I’m not mis­taken, your Global code would only be run if you started up a fake appli­ca­tion in your test using the Helpers class.

    This is all very inter­est­ing, though. I’ve been play­ing around with Cucum­ber, but this cer­tainly war­rants a look. Have you seen a change in how much work it takes to pro­duce a test in Cucum­ber and in Concordion?

    • Ok, but if I run “play test” it works, and if I “play ‘test-only mytest’” it doesn’t get called. Maybe there is a bet­ter way to set the prop­erty? It also worked when I set it in the run­This­Test() method.

      Regard­ing Cucum­ber — I can’t really say, because this is my first go at the whole BDD/TDD thing.

  2. nice post thank you some­times TDD seems hard to imple­ment in play…, btw, which chrome theme are you using?

Leave a Reply

Required fields are marked *.