Cucumber Testing Goodies: Date Language and Chronic
If you've worked with Cucumber for even a short period of time you've almost certainly run up against the question of how to deal with dates and times in your gherkin. There have been various attempts at working with dates/time in gherkin, a majority of which end up creating brittle and difficult to maintain tests. You could hard-code dates and times, but we all know that is a bad idea, not to mention unmaintainable in the long run. Furthermore, one of the reasons for using Cucumber is to record user stories in plain English. Lets consider an example scenario and some gherkin which may end up causing trouble down the road:
In this scenario we are hard-coding dates, which will cause us headaches when we go to maintain this code down the road. A more maintainable scenario which uses plain English could read like this:
You may be thinking "What's the big deal? 'Today' (or 'yesterday', or 'tomorrow' for that matter) is easy to parse out, and I bet I could whip together a little helper method to get the right date for "90 days from today" Done and done. Or is it?
Simple rolling dates such as "yesterday" or "tomorrow" may not be too difficult to work with as you can just subtract or add a day to the current date, but what about more complex dates? Complex dates described in natural language such as "five months ago" and "second Tuesday July 2014" pose more of a challenge, and rolling your own date parsing logic is almost never a good idea, not to mention almost certainly out of scope for the problem you're trying to solve.
So given that hard-coding is out the question, rolling your own parser is out of scope, and we want to encourage natural language in our gherkin how do you handle these kind of dates? Enter the Chronic gem.
This is a maintainable solution which has the benefit of being in English. Lets say that instead of "90 days from now" your product owner changes his mind and decides that he want to schedule a review "60 days from today at noon". Chronic can handle the time aspect just as easily using the exact same step definition.
Here are some other examples of what Chronic can do (execute from irb):
These are just a few examples what Chronic is capable of, but as you can see it greatly increases your ability to write maintainable, user-friendly gherkin using the English language.
Scenario: Scheduling a 90-day Review
Given an employee is hired 3/17/2013
When I schedule a review
Then the review should be scheduled for 6/17/2013
In this scenario we are hard-coding dates, which will cause us headaches when we go to maintain this code down the road. A more maintainable scenario which uses plain English could read like this:
Scenario: Scheduling a 90-day Review
Given an employee is hired "today"
When I schedule a review
Then the review should be scheduled for "90 days from today"
You may be thinking "What's the big deal? 'Today' (or 'yesterday', or 'tomorrow' for that matter) is easy to parse out, and I bet I could whip together a little helper method to get the right date for "90 days from today" Done and done. Or is it?
Simple rolling dates such as "yesterday" or "tomorrow" may not be too difficult to work with as you can just subtract or add a day to the current date, but what about more complex dates? Complex dates described in natural language such as "five months ago" and "second Tuesday July 2014" pose more of a challenge, and rolling your own date parsing logic is almost never a good idea, not to mention almost certainly out of scope for the problem you're trying to solve.
So given that hard-coding is out the question, rolling your own parser is out of scope, and we want to encourage natural language in our gherkin how do you handle these kind of dates? Enter the Chronic gem.
Chronic
The Chronic gem is a natural language date/time parser for Ruby. Because it parses phrases such as the ones described above you can incorporate English language phrases into your gherkin which can be easily parsed out in your step definitions. Lets examine what the step definitions for our example above using Chronic look like:Scenario: Scheduling a follow-up appointment after a sale
Given(^an employee is hired "(.*?)"$/) do |hire_date_s|
@hire_date = Chronic.parse(hire_date_s)
end
When(^I schedule a review$/) do
@review_scheduler.schedule(@hire_date)
end
Then(^the review should be scheduled for "(.*?)"$/) do |expected_review_date_s|
expected_review_date = Chronic.parse(expected_review_date_s)
Review.where("review_date = :review_date", {:review_date => expected_review_date}).exists?.should be true
end
#other steps
This is a maintainable solution which has the benefit of being in English. Lets say that instead of "90 days from now" your product owner changes his mind and decides that he want to schedule a review "60 days from today at noon". Chronic can handle the time aspect just as easily using the exact same step definition.
Here are some other examples of what Chronic can do (execute from irb):
1.8.7 :001 > require 'rubygems'
=> true
1.8.7 :002 > require 'chronic'
=> true
1.8.7 :003 > Chronic.parse('the second Tuesday July 2014')
=> Tue Jul 02 20:14:00 -0400 2013
1.8.7 :004 > Chronic.parse('five months ago')
=> Thu Jan 17 21:41:29 -0500 2013
1.8.7 :014 > Chronic.parse("one week from today at noon")
=> Mon Jun 24 12:00:00 -0400 2013
1.8.7 :015 > Chronic.parse("8 hours ago")
=> Mon Jun 17 14:00:02 -0400 2013
These are just a few examples what Chronic is capable of, but as you can see it greatly increases your ability to write maintainable, user-friendly gherkin using the English language.