Posts Tagged ‘Rails’

Making your Rails controllers respond

Friday, May 8th, 2009

I was looking at Google Analytics today and found that the following searhes often end up here:

webrat no action responded to /. actions: index

and

"no action responded to" webrat

It’s good to see that people are trying out webrat, presumably with cucumber to test their apps.

The problem here is most likely that you need to add a method to your controller to handle the incoming request.  So if you are getting the message

no action responded to new

you would want to go to the controller that you are testing and add something like this:

1
2
3
def new
    #TODO make new do something
end

Don’t forget to add a view such as app/views/example/new.html.erb that corresponds to the action you just added to your controller.

Also, learn to find the TODO annotation again with rake.

Cucumber lost the letter a on windows

Tuesday, May 5th, 2009

Find that the letter ‘a’ is missing from your output from cucumber in windows? Try adding the line

$KCODE=''

to your features/support/env.rb file.

It worked for me.

Chores: a test driven website – part 5 (the admission)

Friday, January 16th, 2009

This is part of the Chores series of posts

While committing Chores to git after the last refactoring, I’m thinking to myself. Who am I kidding? I’m going to write a post on functional testing? But I suck at functional testing! Well, we’ll see how it goes.

First, I’d like to install a gem called Machinist (this is also available as a plugin, if you prefer). Machinist is a drop in replacement for fixtures, allowing you to make (save) validated test data with a little config and a method call. You can read all about it in the readme, linked above. To install the gem simply:

$ gem install notahat-machinist

And be sure to configure your gem by adding the following to config/environments/test.rb

config.gem "notahat-machinist", :lib => 'machinist', :version => '0.3.1'

In fact, something I failed to cover is configuring your gems. Something you might want to take a minute to do.

Blueprints

Machinist uses “blueprints” to create records in the database for testing. These blueprints tell machinist how to create a valid record / object. These are stored in a blueprints file. So lets create a file in test/ called blueprints.rb that contains:

1
2
3
Chore.blueprint do
  description "Put the dirty clothes in the hamper"
end

and require the blueprint file in our test helper (test/test_helper.rb)

1
2
require 'machinist'
require File.expand_path(File.dirname(__FILE__) + "/blueprints")

On to the functional tests, or testing of the controllers. I used the rails scaffold functional tests as the starting point for my functional tests for chore. Here’s how the first run ended up. You might notice that there is not test for update, that’s because I’m not sure update is needed at the moment and since our cucmber feature (remember we are trying to get that working) didn’t say anything about updating, I’m just leaving it off for now.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
require 'test_helper'
 
class ChoresCanCrud < ActionController::TestCase
  tests ChoresController
 
  specify "test that it should respond to index" do
    get :index
    assert_response :success
    assert_not_nil assigns(:chores)
  end
 
  specify "that it should pass chore on new" do
    get :new
    assert_response :success
    assert_not_nil assigns(:chore)
  end
 
  specify "that it should create chore" do
    assert_difference 'Chore.count' do
      post :create, :chore => { :description => 'My Chore' }
    end
 
    assert_redirected_to chores_url
  end
 
  specify "that it will destroy a chore, muahahahahaha" do
    chore = Chore.make
    assert_difference 'Chore.count', -1 do
      delete :destroy, :id => chore.id
    end
    assert_redirected_to chores_url
  end
end

The corresponding Chores controller looks like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ChoresController < ApplicationController
  def index
    @chores = Chore.find(:all)
  end
 
  def new
    @chore = Chore.new
  end
 
  def create
    @chore = Chore.new(params[:chore])
    @chore.save
    redirect_to chores_url
  end
 
  def destroy
    chore = Chore.find(params[:id])
    chore.destroy
    redirect_to chores_url
  end
end

Additionally, I had to make a couple views to get the tests to run. They are skeletal at this point, barely more than an empty file. In fact ./app/views/chores/index.html.erb is an empty file and ./app/views/chores/new.html.erb is

1
2
3
4
5
6
7
8
9
10
11
<% form_for(@chore) do |f| %>
  <%= f.error_messages %>
 
  <p>
    <%= f.label :description %><br />
    <%= f.text_field :description %>
  </p>
 
  <%= f.submit "Add" %>
  </p>
<% end %>

Looking back at the functional tests, this seems a little on the light side. For instance the tests passed without ever checking to see what happened with save of Chore. I’m going to go back and add a couple mote tests. Adding the following two tests

1
2
3
4
5
6
7
8
9
  specify "that it should send a message that chore was created" do
    post :create, :chore => { :description => 'My Chore' }
    assert_equal "Chore added.", flash[:message]
  end
 
  specify "that it should send a message that chore creation failed" do
    post :create, :chore => { :description => nil }
    assert_equal "Error saving Chore.", flash[:error]
  end

changes the create event to be

1
2
3
4
5
6
7
8
9
10
  def create
    @chore = Chore.new(params[:chore])
    if @chore.save
      flash[:message] = "Chore added."
      redirect_to chores_url
    else
      flash[:error] = "Error saving Chore."
      render :action =>  "new"
    end
  end

Well that should be enough to move on with our cucumber feature and a solid beginning for our chore controller. Running rake features again shows us that we indeed have passed the next step. It is now looking for a field that isn’t there.

$ 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
    And I fill in the chore with "My first chore"  # features/step_definitions/chores_steps.rb:5
      Could not find field labeled "chore[name]" (RuntimeError)
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/flunk.rb:4:in 'flunk'
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/locators.rb:16:in 'field_labeled'
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/locators.rb:10:in 'field'
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/scope.rb:183:in 'locate_field'
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/core/scope.rb:41:in 'fills_in'
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/rails.rb:88:in 'send'
      c:/Ruby/lib/ruby/gems/1.8/gems/webrat-0.3.4/lib/webrat/rails.rb:88:in 'method_missing'
      c:/Ruby/lib/ruby/gems/1.8/gems/actionpack-2.2.2/lib/action_controller/integration.rb:498:in '__send__'
      c:/Ruby/lib/ruby/gems/1.8/gems/actionpack-2.2.2/lib/action_controller/integration.rb:498:in 'method_missing'
      ./features/step_definitions/chores_steps.rb:6:in 'And /^I fill in the chore with "(.*)"$/'
      features/define_chores.feature:9:in `And I fill in the chore with "My first chore"'
    And I press Add                                # features/step_definitions/chores_steps.rb:9
    Then I should see "Chore added."               # features/step_definitions/webrat_steps.rb:83
    And I should see "My first chore"              # features/step_definitions/webrat_steps.rb:83
 
 
1 scenario
2 steps passed
1 step failed
3 steps skipped

It appears that I had intended to name the field Chore.name, but when it came around time to name the field, I named it Chore.description. I’ll go ahead and modify my step definition to read ‘chore[description]‘ and try again.

Much better this time. Looks like the feature is looking for the flash message to be displayed on screen. Currently we aren’t doing that, so I’ll need to drop that in.

$ 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
    And I fill in the chore with "My first chore"  # features/step_definitions/chores_steps.rb:5
    And I press Add                                # features/step_definitions/chores_steps.rb:9
    Then I should see "Chore added."               # features/step_definitions/webrat_steps.rb:83
      expected: /Chore added./m,
           got: "\n" (using =~)
      Diff:
      @@ -1,2 +1,2 @@
      -/Chore added./m
      +"\n" (Spec::Expectations::ExpectationNotMetError)
      ./features/step_definitions/webrat_steps.rb:84:in `Then /^I should see "(.*)"$/'
      features/define_chores.feature:11:in `Then I should see "Chore added."'
    And I should see "My first chore"              # features/step_definitions/webrat_steps.rb:83
 
 
1 scenario
4 steps passed
1 step failed
1 step skipped

Well, this isn’t beautiful but if I add this layout (as app/views/layout/application.html.erb), the flash will display and the feature step will pass and we can move on.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
       "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<title>Chores
</title>
</head>
<body>
 
<%= flash[:error] %>
<%= flash[:message] %>
 
<%= yield %>
 
</body>
</html>

that passes our next step, and we are onto the step that says ‘And I should see “My first chore”‘. This means that we are expecting it to have redirected us to index and that the new record that we just added should display on screen. Thus far I have placed nothing on the index view, so I will add the following.

1
2
3
<% @chores.each do |chore| %>
  <%=h chore.description %>
<% end %>

and now cucumber gives us some of it’s green cucumbery goodness with a passing test.

$ 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
    And I fill in the chore with "My first chore"  # features/step_definitions/chores_steps.rb:5
    And I press Add                                # features/step_definitions/chores_steps.rb:9
    Then I should see "Chore added."               # features/step_definitions/webrat_steps.rb:83
    And I should see "My first chore"              # features/step_definitions/webrat_steps.rb:83
 
 
1 scenario
6 steps passed

Now I’ll commit to git and script/server to take a look at what our testing has created. It looks good, once I remove index.html from the public directory, I can see the Add Chore link from the home page. Clicking on it gives me a validated form that allows me to enter a chore entry and save it. We also have tests that tell us that it’s all working well and functioning as expected.

We have taken a look at implementing a feature with Test::Unit, webrat and cucumber and it’s really not all that difficult to do. Really the process from this point on is rinse, repeat, refactor. I’m not sure at this point if I’ll return to post on Chores or not. It’s been interesting documenting my process as I go along, but it’s also been a weight slowing me down. In any case, I hope you have enjoyed this series on test-driven development and maybe have decided to try it out for yourself. I hope so, and if you have kids, maybe check out chores when it’s done.