Mar 25, 2009

Baby Step #2 with Rails, RSpec and Cucumber

In my previous post I took my first stab at Cucumber; first writing a failing Scenario, then getting it to pass, then writing a failing Spec and getting it to pass. The scenario in the previous post was overly simple, in fact I was able to complete it without writing any code at all. My goal this time is to continue my education with a slightly more realistic scenario.

One way to attack an application is in the same order it's used. In the case of a blog the author will need to add content before the site has any value, so that seems like a reasonable starting point.

# RAILS_ROOT/features/author_publish.feature
Feature: author publish
As an author
I want to publish a post
So that it can be read

Scenario: publish a post
Given I am on the post creation page
When I add a new post
Then I should see a page for the new post

The narrative for this feature was easy but deciding on how to approach the first scenario wasn't. In the end after browsing around the web and reading tons of material it was another post from Ben Maybe called "Imperative vs Declarative Scenarios in User Stories" that seemed to help me decide.

With that feature in place running cucumber:
./script/cucumber features/author_publish.feature

Produces the following:
Feature: author publish
As an author
I want to publish a post
So that it can be read

Scenario: publish a post # features/author_publish.feature:6
Given I am on the post creation page # features/step_definitions/webrat_steps.rb:6
Can't find mapping from "the post creation page" to a path. (RuntimeError)
/home/mgreenly/Projects/blog/features/support/paths.rb:11:in `/^I am on (.+)$/'
features/author_publish.feature:7:in `Given I am on the post creation page'
When I add a new post # features/author_publish.feature:8
Then I should see a page for the new post # features/author_publish.feature:9

1 scenario
1 failed step
2 undefined steps

You can implement step definitions for missing steps with these snippets:

When /^I add a new post$/ do
pending
end

Then /^I should see a page for the new post$/ do
pending
end

The problem here is cucumber doesn't know what to map the phrase 'the post creation page' to. This can be fixed by adding the following code to the navigation helper.
# RAILS_ROOT/features/support/paths.rb
def path_to(page_name)
case page_name

when /the homepage/
root_path

when /the post creation page/
post_path("new")

else
raise "Can't find mapping from \"#{page_name}\" to a path."
end
end

Now we run cucumber again:
./script/cucumber features/author_publish.feature

And it produces the following:
Feature: author publish
As an author
I want to publish a post
So that it can be read

Scenario: publish a post # features/author_publish.feature:6
Given I am on the post creation page # features/step_definitions/webrat_steps.rb:6
undefined method `post_path' for # (NoMethodError)
/home/mgreenly/Projects/blog/features/support/paths.rb:9:in `/^I am on (.+)$/'
features/author_publish.feature:7:in `Given I am on the post creation page'
When I add a new post # features/author_publish.feature:8
Then I should see a page for the new post # features/author_publish.feature:9

1 scenario
1 failed step
2 undefined steps

You can implement step definitions for missing steps with these snippets:

When /^I add a new post$/ do
pending
end

Then /^I should see a page for the new post$/ do
pending
end

Now it's complaining that it can't find a mapping for the Post resource. That makes sense since in my previous round I only created a controller. Instead of laboring through the process to create the Post model why not cheat and use the built in generator.
./script/generate rspec_scaffold -f Post title:string body:text

Obvious, going this route means I've broken my previously completed feature, so out of curiosity lets check it out.

Running cucumber on the original feature:
./script/cucumber features/reader_browse.feature

Produces the following:
Feature: browsing
As a reader
I want to browse the site
So that I can read it's content

Scenario: visit the homepage # features/reader_browse.feature:6
When I go to the homepage # features/step_definitions/webrat_steps.rb:10
Then I should see "Hello world!" # features/step_definitions/webrat_steps.rb:93
expected the following element's content to include "Hello world!":
Posts: index
Listing posts
Title
Body
New post
(Spec::Expectations::ExpectationNotMetError)
features/reader_browse.feature:8:in `Then I should see "Hello world!"'

1 scenario
1 failed step
1 passed step


Broken as expected. The problem is that the index page no longer has the "Hello world!" text on it. No problem we'll fix that later, for now lets keep going on the new feature.

When I run cucumber now:
./script/cucumber features/author_publish.feature

I get:
Feature: author publish
As an author
I want to publish a post
So that it can be read

Scenario: publish a post # features/author_publish.feature:6
Given I am on the post creation page # features/step_definitions/webrat_steps.rb:6
When I add a new post # features/author_publish.feature:8
Then I should see a page for the new post # features/author_publish.feature:9

1 scenario
2 undefined steps
1 passed step

You can implement step definitions for missing steps with these snippets:

When /^I add a new post$/ do
pending
end

Then /^I should see a page for the new post$/ do
pending
end

So now the it's obviously finding the 'the post creation page' but the remaining When/Then are still pending.

So now I add the following code:
# RAILS_ROOT/features/step_definitions/publish_steps
When /^I add a new post$/ do
fill_in "Title" , :with => "Some Title"
click_button "Create"
end


Then I run cucumber:
./script/cucumber features/author_publish.feature

And get the following:
Feature: author publish
As an author
I want to publish a post
So that it can be read

Scenario: publish a post # features/author_publish.feature:6
Given I am on the post creation page # features/step_definitions/webrat_steps.rb:6
When I add a new post # features/step_definitions/posts_steps.rb:1
Then I should see a page for the new post # features/author_publish.feature:9

1 scenario
1 undefined step
2 passed steps

You can implement step definitions for missing steps with these snippets:

Then /^I should see a page for the new post$/ do
pending
end

Now I just need to satisfy the 'Then'
# RAILS_ROOT/features/step_definitions/publish_steps
Then /^I should see a page for the new post$/ do
response.should contain("Post was successfully created")
end


Then I run cucumber and get the following:
Feature: author publish
As an author
I want to publish a post
So that it can be read

Scenario: publish a post # features/author_publish.feature:6
Given I am on the post creation page # features/step_definitions/webrat_steps.rb:6
When I add a new post # features/step_definitions/publish_steps.rb:1
Then I should see a page for the new post # features/step_definitions/publish_steps.rb:6

1 scenario
3 passed steps


Everything works, except the feature I broke, but I'll pick up there in the next post.

No comments: