Chores: A test driven website, part 2

This is part of the Chores series of posts

In which I continue to build the chores website and demonstating how simple it is to use a test first methodology. To follow along, please see my previous post.

Doh! I forgot to commit!

When I left off, I had come to a natural stopping point. A place where I was shifting focus from features to unit testing. I like to make my commits when my tests are all passing, however if I’m implementing a new feature, I don’t apply this hueristic since completing a feature will span multiple units of work. So let’s commit this. First let’s see where I am:

$ git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       features/
#       lib/
#       script/cucumber
nothing added to commit but untracked files present (use "git add" to track)

Ok, git sees that I’ve added cucumber to my rails app and that I have added a feature. Let’s add these changes to our commit.

$ git add .

and commit them

$ git commit -m "added feature to create chores"

and finally push a copy out to the repository I set up on my shared host.

$ git push origin master

there all set.

Routing to home

At this point I’m a little confused as to how to proceed test first. We have a failing feature. When I have a failing feature I like to start by making a test to simulate the same error. However in this case, that is difficult at best, and the failing feature has already told me what I need to do, which is create a home_controller

$ ruby script/generate controller Home
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/home
      exists  test/functional/
      create  app/controllers/home_controller.rb
      create  test/functional/home_controller_test.rb
      create  app/helpers/home_helper.rb

.. and map a route to it. To do that I open up my ./config/routes.rb file and add the following route. I like to get rid of the legacy routes so I’ll replace the entire text of the file with the following

[sourcecode language="ruby"]
ActionController::Routing::Routes.draw do |map|
map.root :controller => ‘home’
end

So what are these controllers and routes?

If you are new to Rails, you are probably wondering what we just did. The controller is the C in the MVC pattern that Rails uses. It's role is to handle web serving of the application. It's kind of like an office assistant for your application. It be able to handle frequent requests and delegate them to the proper subject matter expert. When a request comes in for a list of Widgets, it will go to the Widget model and ask it for a list of widgets and then pass that off to the user interface (view) to display in the browser. When the user interface passes it a bag of information and asks it to save the information, the controller will create an instance of the appropriate model from the bag of information and then tell the model it should save the information.

Controllers really shouldn't become too involved in the details, but should delegate well to the objects that know what they are doing. Rails have a nice script to generate controllers, which we used above. In this case we generated a home controller which we will use to handle requests for the application's home page or root.

The second thing we did is define a route. Routes are related to controllers, in that they show the framework how to translate a request such as http://www.exmaple.com/sprocket/new into a method call on a controller. In the case of the above url, it would call the method new on the sprocket controller. You can find your routes in config/routes.rb and view all of your current routes with the rake task

rake routes

On with the show!

Now let's go back to cucumber and see where we are.

$ cucumber features
Story:  Define chores  # features/define_chores.feature
As a parent
I want to define chores
So that I can assign them to my children
  Scenario: Creating a chore       # features/define_chores.feature:6
    Given I am on the homepage    # features/step_definitions/chores_steps.rb:1
      No action responded to index. Actions:  (ActionController::UnknownAction)

Ok, our route is now wired up nicely to the controller, but the home controller does not respond to index. Lets add this to the controller

[sourcecode language="ruby"]
class HomeController < ApplicationController
def index

end
end

and try cucumber again

$ cucumber features
Story:  Define chores  # features/define_chores.feature
As a parent
I want to define chores
So that I can assign them to my children
  Scenario: Creating a chore     # features/define_chores.feature:6
    Given I am on the homepage   # features/step_definitions/chores_steps.rb:1
      Missing template home/index.erb in view path c:/rails/chores/app/views: (ActionView::MissingTemplate)

Ah ha! We are making progress. Our request has passed through the controller and is now looking for a view to render. One thing that I would like to point out is that doing the development in this way really lays out how the pieces of Rails all fit together. Something that we wouldn't have seen if we had chosen to generate a scaffold or we built it piecemeal without the tests metering our pace.

Views

Views are what end up being displayed in the web browser, they are a sort of template. By default, erb will process the template in the context of your request and output the dynamic content. Pretty cool stuff, and it gives you great control over your output.

Well what are we waiting for, let's make a view.

$ touch app/views/home/index.html.erb

In windows you can use your IDE or folder browser to create the file.

$ cucumber features
Story:  Define chores  # features/define_chores.feature
Story:  Define chores
As a parent
I want to define chores
So that I can assign them to my children
 
  Scenario: Creating a chore                          # features/define_chores.feature:6
    Given I am on the homepage                        # features/step_definitions/webrat_steps.rb:6
    When I follow "Add Chore"                         # features/step_definitions/webrat_steps.rb:18
      Could not find link with text or title or id "Add Chore" (Webrat::NotFoundError)
      (eval):2:in '/^I follow "([^"]*)"$/'
      features/define_chores.feature:8:in 'When I follow "Add Chore"'
    And I fill in "chore[name]" with "My first chore" # features/step_definitions/webrat_steps.rb:22
    And I press "Add"                                 # features/step_definitions/webrat_steps.rb:14
    Then I should see "Chore added."                  # features/step_definitions/webrat_steps.rb:93
    And I should see "My first chore"                 # features/step_definitions/webrat_steps.rb:93
 
1 scenario
1 failed step
4 skipped steps
1 passed step
rake aborted!

Hey look! Our first step has passed! Let's give ourselves a pat on the back. We have a website with a dynamic (albeit blank) homepage, that is tested.

Not all quotes are equal

I had an issue with my feature where the double quotes in my feature were translated automatically to open and closed style quotes. This was causing my steps not to match. If you find that you have a bunch of steps that are undefined at this point, that may be the issue.

Catch the passing step fever

The next step is one that is generated by webrat. It is looking for a link to follow called "Add Chore". The problem is that we didn't actually add the link, or anything else to the view. So let's update the view to read

[sourcecode language="ruby"]
<%= link_to "Add Chore", chore_path %>

and fire up cucumber

$ cucumber features
Story:  Define chores  # features/define_chores.feature
As a parent
I want to define chores
So that I can assign them to my children
  Scenario: Creating a chore      # features/define_chores.feature:6
    Given I am on the homepage    # features/step_definitions/chores_steps.rb:1
      undefined local variable or method 'chore_path' for #<actionView::Base:0x4baf67c> (ActionView::TemplateError)
      On line #1 of app/views/home/index.html.erb
 
      1: <%= link_to "Add Chore", chore_path %>
 
      app/views/home/index.html.erb:1

ok, we need to define resource for this, let's add a route and controller.

[sourcecode language="ruby"]
ActionController::Routing::Routes.draw do |map|
map.resources :chores
map.root :controller => 'home'
end

and

$ ruby script/generate controller Chores
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/chores
      exists  test/functional/
      create  app/controllers/chores_controller.rb
      create  test/functional/chores_controller_test.rb
      create  app/helpers/chores_helper.rb
$ cucumber features
Story:  Define chores  # features/define_chores.feature
As a parent
I want to define chores
So that I can assign them to my children
  Scenario: Creating a chore    # features/define_chores.feature:6
    Given I am on the homepage   # features/step_definitions/chores_steps.rb:1
      chore_url failed to generate from {:action=>"show", :controller=>"chores"}
 - you may have ambiguous routes, or you may need to supply additional parameter
s for this route.  content_url has the following required parameters: ["chores",
 :id] - are they all satisfied? (ActionView::TemplateError)
      On line #1 of app/views/home/index.html.erb
 
      1: <%= link_to "Add Chore", chore_path %>

Dang it! still getting an error. Hmmm.. wait a minute. chore_path is generating a show action. That's not what we want to do. We want a new action to be RESTful or maybe an index action if we don't see REST in the future for this app. A quick consultation with the domain expert (me) says that there is no valid reason not follow the RESTful guidelines at this point so let's fix the home/index view to read.

[sourcecode language="ruby"]
<%= link_to "Add Chore", new_chore_path %>

cucumber says...

$ cucumber features
Story:  Define chores  # features/define_chores.feature
As a parent
I want to define chores
So that I can assign them to my children
  Scenario: Creating a chore   # features/define_chores.feature:6
    Given I am on the homepage   # features/step_definitions/chores_steps.rb:1
    When I follow "Add Chore"   # features/step_definitions/webrat_steps.rb:8
      No action responded to new. Actions:  (ActionController::UnknownAction)

Great! That fixed it. Now we need to make sure there is a new action on the chores controller.

class ChoresController < ApplicationController
  def new
 
  end
end
$ cucumber features
Story:  Define chores  # features/define_chores.feature
As a parent
I want to define chores
So that I can assign them to my children
  Scenario: Creating a chore    # features/define_chores.feature:6
    Given I am on the homepage    # features/step_definitions/chores_steps.rb:1
    When I follow "Add Chore"      # features/step_definitions/webrat_steps.rb:8
      Missing template chores/new.erb in view path c:/rails/chores/app/views: (ActionView::MissingTemplate)

cucumber likes it! But we still have an error. Let's create ./app/views/chores/new.html.erb as:

<% form_for(@chore) do |f| %>
  <%= f.error_messages %>
 
  <p>
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </p>
 
  <%= f.submit "Add" %>
  </p>
<% end %>

Hmmm.. wait we are getting ahead of ourselves. We need a model to be able to pass it to our view. We will have to generate that next.

Well we didn't get to Test::Unit this time around, but with a model in sight it's sure to be right around the corner. Until next time..

Tags: , , ,

8 Responses to “Chores: A test driven website, part 2”

  1. [...] post is a continuation of parts 1 and 2 in which I am documenting the steps with which I am creating a rails site and writing matching [...]

  2. jeremy says:

    Scratch that first part – I now see those are the generated comments from running features from the command line.

  3. Wayne Simacek says:

    You have a great site…but I’m really struggling trying to follow through your example for this part. [I'm new to everything, but want to get started with BDD]

    I’m to the point where I have 1 passed step and failing ‘When I follow “Add Chore” ==>When I put in I go back to 1 failed and 5 skipped.

    The error undefined local variable or method `chore_path’ for # (ActionView::TemplateError)
    features/define_chores.feature:7:in `Given I am on the homepage’

    I’ve been researching this for some time with no luck.

  4. Wayne Simacek says:

    I should mention this all started when I added in my index.html.erb

  5. Wayne Simacek says:

    link_to “Add Chore”, chore_path

  6. Jamal says:

    Hi Wayne,

    Thank you, it’s good to see people learning good habits like tdd and bdd. It sounds like the issue you’ve found is because you need to configure a route. Basically a route will set up the named urls such as chore_path. It will also tell rails how to translate a url to a method on your controller.

    To see the routes that are currently in your rails app you can use the rake task:

    rake routes

    Route configuration is done in config/routes.rb where the standard template contains a number of commented examples.

    In this case you would want to add something like:

    
    map.resources :chores
    

    In fact my entire routes.rb looks like:

    
    ActionController::Routing::Routes.draw do |map|
      map.resources :chores
      map.resources :children
      map.root :controller => 'home'
    
      map.open_id_complete 'session', :controller => "sessions", :action => "create", :requirements => { :method => :get }
      map.resource :session
      map.login '/login', :controller => 'sessions', :action => 'new'
      map.logout '/logout', :controller => 'sessions', :action => 'destroy'
    
    end
    
  7. Pamelawask says:

    That was nice. Thank you for sharing this one.

  8. Alex56 says:

    However, under the right circumstances E. ,

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">