Today I'm going to document the process of building the first step definitions for Techlahoma. This will turn the Cucumber stories that we built in part 1 into runnable test scenarios.

In part 1 we saw that Cucumber will give you stubs for your method definitions when you run Scenarios that are missing them. So, to get started I just created a file at features/step_definitions/user_steps.rb and I pasted in the stubs that Cucumber provided.

Given(/^a signed out user$/) do
  pending # express the regexp above with the code you wish you had
end

When(/^he visits the home page$/) do
  pending # express the regexp above with the code you wish you had
end

Then(/^he should see "(.*?)"$/) do |arg1|
  pending # express the regexp above with the code you wish you had
end

When(/^he signs in$/) do
  pending # express the regexp above with the code you wish you had
end

When(/^he fails the signs in$/) do
  pending # express the regexp above with the code you wish you had
end

Given(/^a signed in user$/) do
  pending # express the regexp above with the code you wish you had
end

When(/^he signs out$/) do
  pending # express the regexp above with the code you wish you had
end

When(/^he fails the sign out$/) do
  pending # express the regexp above with the code you wish you had
end

Now when I run the sign in feature I can see that Cucumber is finding the step definitions and knows that they are still pending.

$ cucumber features/users/signin.feature -r features/
Using the default profile...
Feature: Sign In
  As a visiting user
  I want to sign in
  Sign in will happen via GitHub (and possibly other auth providers)

  Scenario: Sucessful sign in     # features/users/signin.feature:6
    Given a signed out user       # features/step_definitions/users_steps.rb:1
      TODO (Cucumber::Pending)
      features/users/signin.feature:7:in `Given a signed out user'
    When he visits the home page  # features/step_definitions/users_steps.rb:5
    Then he should see "Sign In"  # features/step_definitions/users_steps.rb:9
    When he signs in with GitHub  # features/step_definitions/users_steps.rb:13
    Then he should see "Sign Out" # features/step_definitions/users_steps.rb:9

  Scenario: Failed sign in                # features/users/signin.feature:13
    Given a signed out user               # features/step_definitions/users_steps.rb:1
      TODO (Cucumber::Pending)
      features/users/signin.feature:14:in `Given a signed out user'
    When he visits the home page          # features/step_definitions/users_steps.rb:5
    Then he should see "Sign In"          # features/step_definitions/users_steps.rb:9
    When he fails the sign in with GitHub # features/step_definitions/users_steps.rb:17
    Then he should see "Sign In"          # features/step_definitions/users_steps.rb:9
    Then he should see "sorry"            # features/step_definitions/users_steps.rb:9

2 scenarios (2 pending)
11 steps (9 skipped, 2 pending)
0m0.138s

Now we're ready to start filling out the step definitions. Let's just step through the first few and start making them pass.

The first one is easy. There is nothing to do, so we can just remove the pending call, and add a comment noting that the step is intentionally blank.

Given(/^a signed out user$/) do
  # this doesn't need any explicit action
end

The next one is pretty straight forward. Cucumber wraps Capybara, so inside the step definitions we can use standard Capybara syntax.

When(/^he visits the home page$/) do
  visit "/"
end

For the Then he should see "Sign In" step, we can use regular expressions to generalize that step. This means that we can reuse this step definition for any text that we want to locate on the page.

Then(/^he should see "(.*?)"$/) do |text|
  page.should have_content(text)
end

The should syntax requires rspec, so add it to the :test group in Gemfile.

gem "rspec-rails", "~> 2.14.0"

Then do a quick bundle install.

Now the sign in feature will pass the first step (doing nothing), but will fail at visiting the home page.

...
    When he visits the home page  # features/step_definitions/users_steps.rb:5
      No route matches [GET] "/" (ActionController::RoutingError)
      features/users/signin.feature:8:in `When he visits the home page'
...

The visit to the home page is failing because we haven't set up a root route for the app yet. That can be handled by generating a controller and setting it to be the root.

First generate a home controller.

rails g controller home index

Then open config/routes.rb and add a line to set it as the root route.

root "home#index"

Now cucumber will fail with a different error.

...
    Then he should see "Sign In"  # features/step_definitions/users_steps.rb:9
      expected to find text "Sign In" in "Home#index Find me in app/views/home/index.html.erb" (RSpec::Expectations::ExpectationNotMetError)
      features/users/signin.feature:9:in `Then he should see "Sign In"'
...

The test for the "Sign In" link is failing because we haven't added that to the page yet. This is something that we'll want to be on every page, so in the layout right before the yield add this line.

<%%= render 'layouts/header' %>

Then create a file at views/layouts/_header.html.erb with this content.

<%%= link_to "Sign In", "#" %>

For now we're just doing the minimum needed to make the currently failing step pass. As we work through the other steps we'll flesh out this template to be more realistic.

Now the first three steps of the sign in feature are passing.

$ cucumber features/users/signin.feature -r features/
Using the default profile...
Feature: Sign In
  As a visiting user
  I want to sign in
  Sign in will happen via GitHub (and possibly other auth providers)

  Scenario: Sucessful sign in     # features/users/signin.feature:6
    Given a signed out user       # features/step_definitions/users_steps.rb:1
    When he visits the home page  # features/step_definitions/users_steps.rb:5
    Then he should see "Sign In"  # features/step_definitions/users_steps.rb:9
    When he signs in with GitHub  # features/step_definitions/users_steps.rb:13
      TODO (Cucumber::Pending)
      features/users/signin.feature:10:in `When he signs in with GitHub'
    Then he should see "Sign Out" # features/step_definitions/users_steps.rb:9

  Scenario: Failed sign in                # features/users/signin.feature:13
    Given a signed out user               # features/step_definitions/users_steps.rb:1
    When he visits the home page          # features/step_definitions/users_steps.rb:5
    Then he should see "Sign In"          # features/step_definitions/users_steps.rb:9
    When he fails the sign in with GitHub # features/step_definitions/users_steps.rb:17
      TODO (Cucumber::Pending)
      features/users/signin.feature:17:in `When he fails the sign in with GitHub'
    Then he should see "Sign In"          # features/step_definitions/users_steps.rb:9
    Then he should see "sorry"            # features/step_definitions/users_steps.rb:9

2 scenarios (2 pending)
11 steps (3 skipped, 2 pending, 6 passed)
0m0.180s

Now we're ready to actually log the user in, but that's big enough for a post of its own. Next time we'll look at using omniauth to handle authenticating a user through GitHub and Twitter.

You're welcome to check out the repo, and if you'd like to get involved we love pull requests.