I’ve talked before about how important it is to make your BDD glue code as specific as possible, so that anyone reading your Gherkin, knows exactly what that action is supposed to perform. Even my co-workers have noticed this as a problem, and have suggested implementations to help. In a post several years ago, I mentioned the usage of Transformers, without getting into much details, and today, it’s time to rectify that.

Transformers

Within the cucumber glue code, when looking for a variable for substitution for method parameters, often times I end up ‘lazily’ relying on my generic match anything regex "([^"]*)". This is great as a catch all, but what you do with it next is really what makes some of the magic of Cucumber happen. Do you just interpret this parameter as a String, maybe as a List (a little more complicated), or maybe even an Enum (definitely do this if possible). For some input formats, I prefer to use the Cucumber Transform annotation to ensure the custom value I want to capture can be well defined and interpreted by the code. This allows the transformation of a step definition argument to a custom type, giving you full control over how that type is instantiated. For handling complex inputs, such as dates, this can be wonderful, to get the value into the most useful form.

Setup

Setting up a basic transformation is relatively easy. Form your regular expression as usual, and in the method declaration, call the transform. For example:

    @Given("^the user birthdate is set as \\d{2}-\\d{2}-\\d{4}$")
    public void setDOB(@Transform(GherkinDateConverter.class) Date birthdate) {

So, once we have this, how do we setup the actual implementation? It’s relatively simple:

public class GherkinDateConverter extends Transformer<Date> {

    SimpleDateFormat monthDayYearDash = new SimpleDateFormat("MM-dd-yyyy");

    @Override
    public Date transform(String date) {
        return monthDayYearDash.parse(date);
    }
}

Strait forward enough, right? But what if we wanted something a little more complex. Maybe we’re unsure of the format the date might be passed in. Maybe we want to handle multiple use cases. In that case, let’s do something like this:

    @Given("^the user birthdate is set as ([\\d\\/-]+)$")
    public void setDOB(@Transform(GherkinDateConverter.class) Date birthdate) {

And for our converter:

public class GherkinDateConverter extends Transformer<Date> {

    SimpleDateFormat yearMonthDayDash = new SimpleDateFormat("yyyy-MM-dd");
    SimpleDateFormat monthDayYearDash = new SimpleDateFormat("MM-dd-yyyy");
    SimpleDateFormat yearMonthDaySlash = new SimpleDateFormat("yyyy/MM/dd");
    SimpleDateFormat monthDayYearSlash = new SimpleDateFormat("MM/dd/yyyy");

    @Override
    public Date transform(String date) {
        try {
            if (date.matches("\\d{4}-\\d{2}-\\d{2}")) {
                return yearMonthDayDash.parse(date);
            }
            if (date.matches("\\d{2}-\\d{2}-\\d{4}")) {
                return monthDayYearDash.parse(date);
            }
            if (date.matches("\\d{4}\\/\\d{2}\\/\\d{2}")) {
                return yearMonthDaySlash.parse(date);
            }
            if (date.matches("\\d{2}\\/\\d{2}\\/\\d{4}")) {
                return monthDayYearSlash.parse(date);
            }
            if (date.matches("\\d+")) {
                return new Date(Long.valueOf(date));
            }
            Logger.getLogger(GherkinDateConverter.class).error("Date '" + date + "' is not a valid date format");
        } catch (ParseException e) {
            Logger.getLogger(GherkinDateConverter.class).error(e.getMessage());
        }
        return new Date();
    }
}

In this use case, we can pass the date in many formats, including just as a unix timestamp. This makes our Gherkin incredibly flexible, and allows our BDD writers to input the date however they choose.

Testing

So, how do we know we’ve set this up properly? This GherkinDateConverter should really have some tests written against it. I threw together some unit tests, to ensure everything is working as desired, for each input type.

public class GherkinDateConverterTest {

    @Test
    public void badDatePatternTest() {
        GherkinDateConverter gherkinDateConverter = new GherkinDateConverter();
        Date bad = gherkinDateConverter.transform("HelloWorld");
        Assert.assertNotNull(bad);
    }

    @Test
    public void yearMonthDayDashTest() {
        GherkinDateConverter gherkinDateConverter = new GherkinDateConverter();
        Date bad = gherkinDateConverter.transform("2000-01-01");
        Assert.assertEquals(bad, new Date(100, 0, 1));
    }

    @Test
    public void dayMonthYearDashTest() {
        GherkinDateConverter gherkinDateConverter = new GherkinDateConverter();
        Date bad = gherkinDateConverter.transform("01-01-2000");
        Assert.assertEquals(bad, new Date(100, 0, 1));
    }

    @Test
    public void yearMonthDaySlashTest() {
        GherkinDateConverter gherkinDateConverter = new GherkinDateConverter();
        Date bad = gherkinDateConverter.transform("2000/01/01");
        Assert.assertEquals(bad, new Date(100, 0, 1));
    }

    @Test
    public void dayMonthYearSlashTest() {
        GherkinDateConverter gherkinDateConverter = new GherkinDateConverter();
        Date bad = gherkinDateConverter.transform("01/01/2000");
        Assert.assertEquals(bad, new Date(100, 0, 1));
    }

    @Test
    public void epochTest() {
        GherkinDateConverter gherkinDateConverter = new GherkinDateConverter();
        Date bad = gherkinDateConverter.transform("946702800000");
        Assert.assertEquals(bad, new Date(946702800000L));
    }
}

Conclusion

And that’s about it. A fine example of how to use Transforms to easily accept Gherkin raw input, and convert it to some nice/simple code to work with. Stay tuned for next month’s post about using the Gherkin Builder to write your BDD tests, and how these Transforms play easily into allowing non-technical people to write simple BDD test cases.

Leave a comment

Your email address will not be published. Required fields are marked *

X