Sunday, May 17, 2009

Another Link in the Chain, Another Bug Caught by Cucumber

‹prev | My Chain | next›

I left off yesterday having gotten links to previous and next years' meals working. Next up is to work my way out from the detailed code back to the Cucumber scenario. If I have done my job well, the scenario will pass without changes...
cstrom@jaynestown:~/repos/eee-code$ cucumber -n features/browse_meals.feature \
> -s "Browsing a meal in a given year"
Feature: Browse Meals

So that I can find meals made on special occasions
As a web user
I want to browse meals by date

Scenario: Browsing a meal in a given year
Given a "Even Fried, They Won't Eat It" meal enjoyed in 2009
And a "Salad. Mmmm." meal enjoyed in 2008
When I view the list of meals prepared in 2009
Then the "Even Fried, They Won't Eat It" meal should be included in the list
expected following output to contain a <li a>Even Fried, They Won't Eat It</li a> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<h1>Meals from 2009</h1>
<div class="navigation">

|

</div>
<ul></ul>
</body></html>
(Spec::Expectations::ExpectationNotMetError)
features/browse_meals.feature:12:in `Then the "Even Fried, They Won't Eat It" meal should be included in the list'
And the "Salad. Mmmm." meal should not be included in the list
When I follow the link to the list of meals in 2008
Then the "Even Fried, They Won't Eat It" meal should not be included in the list
And the "Salad. Mmmm." meal should be included in the list

1 scenario
1 failed step
4 skipped steps
3 passed steps
Ah, nuts.

Ooh, I forgot to add the type to the meal in the Given step. The CouchDB meals views are predicated on this attribute, so adding it ought to resolve the trouble (in bold):
Given /^a "([^\"]*)" meal enjoyed in (\d+)$/ do |title, year|
date = Date.new(year.to_i, 5, 13)

permalink = "id-#{date.to_s}"

meal = {
:title => title,
:date => date.to_s,
:serves => 4,
:summary => "meal summary",
:description => "meal description",
:type => "Meal"
}

RestClient.put "#{@@db}/#{permalink}",
meal.to_json,
:content_type => 'application/json'
end
Well, that fixes the above error, but the 2008 link is still not showing:
cstrom@jaynestown:~/repos/eee-code$ cucumber -n features/browse_meals.feature \
-s "Browsing a meal in a given year"
Feature: Browse Meals

So that I can find meals made on special occasions
As a web user
I want to browse meals by date

Scenario: Browsing a meal in a given year
Given a "Even Fried, They Won't Eat It" meal enjoyed in 2009
And a "Salad. Mmmm." meal enjoyed in 2008
When I view the list of meals prepared in 2009
Then the "Even Fried, They Won't Eat It" meal should be included in the list
And the "Salad. Mmmm." meal should not be included in the list
When I follow the link to the list of meals in 2008
Could not find link with text or title or id "2008" (Webrat::NotFoundError)
features/browse_meals.feature:14:in `When I follow the link to the list of meals in 2008'
Then the "Even Fried, They Won't Eat It" meal should not be included in the list
And the "Salad. Mmmm." meal should be included in the list

1 scenario
1 failed step
2 skipped steps
5 passed steps
After some quick print $stderr debugging (it's how real programmers debug), I find that the Sinatra app was not parsing the JSON returned from the CouchDB view:
get %r{/meals/(\d+)} do |year|
url = "#{@@db}/_design/meals/_view/by_year?group=true&key=%22#{year}%22"
data = RestClient.get url
@meals = JSON.parse(data)
@year = year

url = "#{@@db}/_design/meals/_view/count_by_year?group=true"
data = RestClient.get url
@count_by_year = data['rows']

haml :meal_by_year
end
Ah geez, I hate having to to spec JSON parsing (twice). Then again, if I had not skipped that in the first place, I would not have hit a problem here. I also could have avoided this by using CouchRest to manage the JSON <=> Ruby serialization. If I mess up like this again, I will have to switch. I may end up switching anyway.

I choose not to spec the JSON parsing. I would not be testing behavior, just implementation. Besides, I would have to jump through the same kind of hoops that I had to the other night for RestClient calls. The updated implementation:
  url = "#{@@db}/_design/meals/_view/count_by_year?group=true"
data = RestClient.get url
@count_by_year = JSON.parse(data)['rows']
With that, the scenario fails in exactly the same way!
cstrom@jaynestown:~/repos/eee-code$ cucumber -n features/browse_meals.feature \
-s "Browsing a meal in a given year"
Feature: Browse Meals

So that I can find meals made on special occasions
As a web user
I want to browse meals by date

Scenario: Browsing a meal in a given year
Given a "Even Fried, They Won't Eat It" meal enjoyed in 2009
And a "Salad. Mmmm." meal enjoyed in 2008
When I view the list of meals prepared in 2009
Then the "Even Fried, They Won't Eat It" meal should be included in the list
And the "Salad. Mmmm." meal should not be included in the list
When I follow the link to the list of meals in 2008
Could not find link with text or title or id "2008" (Webrat::NotFoundError)
features/browse_meals.feature:14:in `When I follow the link to the list of meals in 2008'
Then the "Even Fried, They Won't Eat It" meal should not be included in the list
And the "Salad. Mmmm." meal should be included in the list

1 scenario
1 failed step
2 skipped steps
5 passed steps
How badly did I mess up last night?

This failure is caused by my implementation of the link to the next year's meals:
    def link_to_next_year(current, couch_view)
next_result = couch_view.detect do |result|
result['key'].to_i > current.to_i
end
if next_result
%Q|<a href="/meals/#{next_result['key']}">#{next_result['key']}</a>|
else
""
end
end
Somehow, I thought this would work both for the next and previous years—the latter by reversing the list of years. That does not work because, even with the years reversed when sending them to this helper, I am still asking for a year that is greater than the current year. I was so badly confused that I am not even sure how much sense that makes. What I expected to happen was this:
describe "link_to_next_year" do
before(:each) do
@count_by_year = [{"key" => "2008", "value" => 3},
{"key" => "2009", "value" => 3}]
end
it "should link to the previous before the current one" do
link_to_next_year(2009, @count_by_year.reverse).
should have_selector("a",
:href => "/meals/2008")
end
But that fails with no output.

To get this working I need to tell link_to_next_year to link to the previous year as needed. First off, I will rename the function to link_to_year_in_set. The default should be the subsequent year, but and option should link to the previous year.

I re-work the implementation such that the :previous option drives the detection Proc used as well as the reversal of the set:
    def link_to_year_in_set(current, couch_view, options={})
compare_years = options[:previous] ?
Proc.new { |year, current_year| year < current_year} :
Proc.new { |year, current_year| year > current_year}

next_result = couch_view.
send(options[:previous] ? :reverse : :map).
detect{|result| compare_years[result['key'].to_i, current.to_i]}

if next_result
%Q|<a href="/meals/#{next_result['key']}">#{next_result['key']}</a>|
else
""
end
end
With that fixed (and all of the code updated to use the new helper), I give the Cucumber scenario one last try and find it working as expected:
cstrom@jaynestown:~/repos/eee-code$ cucumber -n features/browse_meals.feature \
> -s "Browsing a meal in a given year"
Feature: Browse Meals

So that I can find meals made on special occasions
As a web user
I want to browse meals by date

Scenario: Browsing a meal in a given year
Given a "Even Fried, They Won't Eat It" meal enjoyed in 2009
And a "Salad. Mmmm." meal enjoyed in 2008
When I view the list of meals prepared in 2009
Then the "Even Fried, They Won't Eat It" meal should be included in the list
And the "Salad. Mmmm." meal should not be included in the list
When I follow the link to the list of meals in 2008
Then the "Even Fried, They Won't Eat It" meal should not be included in the list
And the "Salad. Mmmm." meal should be included in the list

1 scenario
8 passed steps
The lesson to learn from tonight is that Cucumber scenarios can really save you—even when you think you are implementing something relatively simple.
(commit)

No comments:

Post a Comment