Dates and Cucumber with Java
Cucumber adds behavioural tests to Java but how it deals with dates as input variables poses a few issues. Here's how I worked around the issue.
Cucumber adds behavioural tests to Java; its Gherkin markup makes it easy to write descriptive tests. The way it deals with dates as input variables, however, does pose a few issues. Below I’ve presented the issue and the way that I worked around the problem.
This post assumes that you already have decent understanding for Java, Cucumber and Behavioural Driven Development (BDD). For more information on any of the topics, see the links section at the bottom.
The Problem with Dates
To demonstrate, let’s set up a simple example of a test for a calculation that figures out the earliest of two or more dates. The feature file might have a scenario such as the following:
Scenario: Simple formatting of dates with annotation
Given the dates 2018-01-01 and 2018-01-31
When the earliest date calc is run
Then the earliest date is 2018-01-01
For which we can create this simple method in the steps binding the scenario to code:
private List<Date> dates;
@Given("^the dates (.*) and (.*)$")
public void givenDates(
@Format("yyyy-MM-dd") Date first,
@Format("yyyy-MM-dd") Date second) {
dates.add(first);
dates.add(second);
}
The use of a format string to define the dates is OK but what about the following situation. I now want to pass the data through as a list.
Given the dates: 2018-01-01,2018-01-31
With a related step that takes a list of dates:
@Given("^the list of dates: (.*)$")
public void dateList(List<Date> dates) {
this.dates = dates;
}
The format tag won’t work in this case as it cannot be applied to a list.
Another scenario is where I want to pass the dates as one of the properties within a complicated object. Consider a person object with a first name, last name and date of birth and a step that takes a table list of them; Cucumber will use the accessor method on the object for each property listed. Again in this example, the format of the date will not be accepted:
Given the following people:
| firstName | lastName | dateOfBirth |
| John | Doe | 1990-06-01 |
| Jane | Doe | 1995-01-01 |
The person object might be a business class that you don’t want to have to write a new mapper for. In theory, take the date string in as a parameter and format it yourself to construct a new person instance, but the following would be a lot nicer:
@Given("^the following people:$")
public void givenPeople(List<Person> people) {
...
}
Out of the box, my code above simply will not work. The exception reported is a problem converting the date string into the date.
cucumber.deps.com.thoughtworks.xstream.converters.ConversionException:
Couldn't convert "1990-06-01" to an instance of: [class java.util.Date]
Default formatting
The quickest way to workaround this issue is to reformat the test case so that it uses the expected default formats used by the underlying parser. Cucumber is using XStream in the background. It expects the following two date formats:
M/d/yy
MMM d, yyyy
Note the second uses the three letter abbreviation for the month, so it is locale specific - language changes in spelling from country to country. The first uses the US version, which for a European is annoying - it is bound to cause a mistake as we generally put the month between the day and year, no matter what format we use.
So the examples above could be changed to the following to work. First the object creation with a table:
Given the following people:
| firstName | lastName | dateOfBirth |
| John | Doe | May 1, 1990 |
| Jane | Doe | Jan 1, 1995 |
This long format, however, will not work for a list because it has a comma in it. Thankfully a table can be used to create a simple list using:
Given the list of dates:
| Jan 1, 2018 |
| May 2, 2018 |
The step for this is:
@Given("^the list of dates:$")
public void dateList(List<Date> dates) {
}
The following will work, but I won’t use it for the above stated reasons:
Given the list of dates: 1/1/18, 1/31/18
And its associated step definition:
@Given("^the list of dates: (.*)$")
public void dateList(List<Date> dates) {
}
Customising a Formatter
Cucumber does not appear to have a means to specify a general date format for an overall test case - at least not with a handy annotation, such as @Format
shown above for use with a Date argument.
It would be nice to have a class level annotation that sets up the date format for all tests therein.
There is the possibility of defining one for XStream and registering in an @Before
method, however, I don’t like this approach. It will pollute test code with unnecessary lines.
Writing tests should be as stream lined as possible. The more code, whether utility methods and classes, or extra statements required to set up the test case, the harder ultimately to maintain going forward. The most concise expression of the test case will be the cleanest.
An annotation provides a concise solution. Code that references the underlying XStream implementation is not welcome in the test code as it uncovers some of the inner workings of Cucumber that we really don’t need to see.
Building this into the Cucumber framework would be the appropriate solution. It’s open source so this might be an interesting side project if someone has the time to tackle it. Write a blog post if you do and let me know.
Links
For more information about, and links to download the Java programming language, try here: https://java.com/en/download/
Cucumber allows tests to be specificed as behavioural tests, with support for many languages including Java. It uses the Gherkin specification language. More information can be found here: https://cucumber.io or for behavioural driven development in general here: https://en.wikipedia.org/wiki/Behavior-driven_development
Find more information about XStream here: http://x-stream.github.io
Code
I've created a GitHub Gist with some of the code. It contains the feature definition and the associated steps required.