Friday, June 26, 2009

How Many Ways Can I Screw Up a Simple Form?

‹prev | My Chain | next›

Having built my feedback form and the action to which it submits, I am ready to work my way back out to the Cucumber scenario driving this particular feature:



Telling Webrat to click the "submit" button is not specific enough. Webrat needs a value or id attribute. So I will rewrite that step as:
When I click the "Send Comments" button
I can define that step with:
When /^click the "([^\"]+)" button$/ do |button_text|
click_button button_text
end
When I run this scenario, I find:
  Scenario: Give feedback to the authors of this fantastic site
Given 25 yummy meals
When I view the site's homepage
And I click "Send us comments"
Then I should see a feedback form
When I fill out the form with effusive praise
And click the "Send Comments" button
Could not find button "Send Comments" (Webrat::NotFoundError)
features/site.feature:53:in `And click the "Send Comments" button'
Then I should see a thank you note
Hunh. Cannot find the "Send Comments" button? I was sure I had that in the Haml template:
...
%label
Message
%textarea{:name => "message", :rows => 8, :cols => 55}
%input{:type => "submit", :name => "Send Comments"}
Ah. I gave the button a name, but not a value. Another obvious mistake on my part caught by Cucumber. I give the button a proper name:
...
%label
Message
%textarea{:name => "message", :rows => 8, :cols => 55}
%input{:type => "submit", :name => "b", :value => "Send Comments"}
And then I re-run the Cucumber scenario, only to find:
  Scenario: Give feedback to the authors of this fantastic site
Given 25 yummy meals
When I view the site's homepage
And I click "Send us comments"
Then I should see a feedback form
When I fill out the form with effusive praise
And I click the "Send Comments" button
PATH_INFO must start with / (Rack::Lint::LintError)
(eval):7:in `get'
features/site.feature:53:in `And I click the "Send Comments" button'
Then I should see a thank you note
This error I do not quite understand. If I am working in the top level URL space of a site (e.g. http://eeecooks.com/feedback), then I expect any form submitted without an absolute URL to be relative to the top-level namespace (i.e. email should be submitted to http://eeecooks.com/email). No matter, I change:
%form{:action => "email"}
to:
%form{:action => "/email"}
And finally that step is passing. The last step is to verify that the user now sees a "thanks for the feedback" message. It is a step so simple that it almost seems not worth bothering. Still, it cannot hurt to define, especially since the step is so easy to define:
Then /^I should see a thank you note$/ do
response.should have_selector("h1", :content => "Thank You")
end
Finally, I get to see my simple feedback scenario is all of its glory:
  Scenario: Give feedback to the authors of this fantastic site
Given 25 yummy meals
When I view the site's homepage
And I click "Send us comments"
Then I should see a feedback form
When I fill out the form with effusive praise
And I click the "Send Comments" button
Then I should see a thank you note
expected following output to contain a <h1>Thank You</h1> tag:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body><h1>Not Found</h1></body></html>
(Spec::Expectations::ExpectationNotMetError)
features/site.feature:54:in `Then I should see a thank you note'
Aaarrrgh! Not found?! How can that be?

After much soul searching and questioning of my ability as a developer, I finally ask myself how could it not be found? It is clearly defined in my Sinatra application:
post '/email' do
message = <<"_EOM"
From: #{params[:name]}
Email: #{params[:email]}

#{params[:message]}
_EOM

Pony.mail(:to => "us _at_ eeecooks.com".gsub(/\s*_at_\s*/, '@'),
:subject => params[:subject],
:body => message)

haml :email
end
The answer, of course, is in the first word of that code snippet: post. The /email action is only defined for POST actions, not GETs. Changing the form action definition in the Haml template will resolve everything:
%form{:action => "/email", :method => "post"}
Finally, I get:
cstrom@jaynestown:~/repos/eee-code$ cucumber features -n \
-s "Give feedback to the authors of this fantastic site"
Sinatra::Test is deprecated; use Rack::Test instead.
Feature: Site

So that I may explore many wonderful recipes and see the meals in which they were served
As someone interested in cooking
I want to be able to easily explore this awesome site

Scenario: Give feedback to the authors of this fantastic site
Given 25 yummy meals
When I view the site's homepage
And I click "Send us comments"
Then I should see a feedback form
When I fill out the form with effusive praise
sendmail: 553 jaynestown.mail.rr.com does not exist
And I click the "Send Comments" button
Then I should see a thank you note

1 scenario
7 passed steps
Oops. An actual email is trying to go out there. If I had sendmail configured correctly, I would be spamming myself right now.

Cucumber is an awesome full-stack testing tool, but, at least in this case, I do not want to test the entire stack. What I want to do is stub out the Pony.mail call right before the form is submitted:
When /^I click the "([^\"]*)" button$/ do |button_text|
# Don't send email in tests
Pony.stub!(:mail)
click_button button_text
end
That will not work because Cucumber don't do stubbing:
  Scenario: Give feedback to the authors of this fantastic site
Given 25 yummy meals
When I view the site's homepage
And I click "Send us comments"
Then I should see a feedback form
When I fill out the form with effusive praise
And I click the "Send Comments" button
undefined method `stub!' for Pony:Module (NoMethodError)
features/site.feature:53:in `And I click the "Send Comments" button'
Then I should see a thank you note
Cucumber really is a full stack testing framework. Fortunately, Bryan Helmkamp has written some nice instructions for including RSpec stubbing and mocking in Cucumber. Following those instructions, I add this to my Cucumber's support/env.rb:
# RSpec mocks / stubs
require 'spec/mocks'

Before do
$rspec_mocks ||= Spec::Mocks::Space.new
...
end

After do
begin
$rspec_mocks.verify_all
ensure
$rspec_mocks.reset_all
end
...
And now, I am done with this scenario:



Tomorrow, I will likely add another small feature to the feedback mechanism before moving onto RSS feeds.

No comments:

Post a Comment