コントローラーをテストしてみる

コントローラーのテストも、test/functionalフォルダ以下に自動生成されていた。内容は以下。つまり、RESTなアクションについては、すぐにでも使えそうなテストとして既に準備されていた。

  • 自分なりに理解したテストの内容は、コメントにして追記しておいた。
      • HTTPメソッドにheadなんていうのもあることに気付いたので調べてみた。「Studying HTTP」 HTTP Method 素晴らしいページに感謝です!
require File.dirname(__FILE__) + '/../test_helper'

class SlipsControllerTest < ActionController::TestCase
  def test_should_get_index
    # 「link_to "Index", :action => :index」リンクを手動でクリックすることと同等の動作をシミュレートしている。
    # 「get アクション名, パラメーター, セッション, フラッシュ」の書式でオプションを指定できる。
    # パラメーター、セッション、フラッシュはハッシュで指定する。
    # その他にもpost、put、delete、headメソッドが準備されている。(書式は同じ)
    get :index

    # 上記indexの呼び出しが正常に処理されたことを確認している。
    assert_response :success

    # assigns(:slips)は、インスタンス変数@slipsの内容を返す。
    # つまり、@slipsがnilでないことを確認している。
    assert_not_nil assigns(:slips)
  end

  def test_should_get_new
    get :new
    assert_response :success
  end

  def test_should_create_slip
    # ブロックの実行前後でレコード数が変化していることを確認している。
    assert_difference('Slip.count') do
      # postメソッドでパラメーターに{:slip=>{}}を指定して呼び出している。
      # もし「post :create, { :slip=>{:number=>1} }」のように指定しておけば...
      # 上記は「text_field :slip, :number」というフォームに「1」を入力して送信ボタンを押す動作をシミュレートしていることになる。
      post :create, :slip => { }
    end

    # リダイレクト先がslip_path(@slip)であることを確認している
    assert_redirected_to slip_path(assigns(:slip))
  end

  def test_should_show_slip
    # slips(:one).idは、slips.ymlファイルのレコード名がoneのidを返す
    get :show, :id => slips(:one).id
    assert_response :success
  end

  def test_should_get_edit
    get :edit, :id => slips(:one).id
    assert_response :success
  end

  def test_should_update_slip
    put :update, :id => slips(:one).id, :slip => { }
    assert_redirected_to slip_path(assigns(:slip))
  end

  def test_should_destroy_slip
    assert_difference('Slip.count', -1) do
      delete :destroy, :id => slips(:one).id
    end

    assert_redirected_to slips_path
  end
end
  • テスト内容がおおよそ理解できたので、Railsが自動生成した状態でrake test:functionalsを実行してみた。
  • ところが結果は以下のようにズラズラと何かが表示され、エラー2件が発生してしまった...。
$ rake test:functionals
(in /Users/bebe/railsapp/test_slip202)
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader.rb" "test/functional/slips_controller_test.rb" 
Loaded suite /Library/Ruby/Gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader
Started
/Users/bebe/railsapp/test_slip202/app/helpers/application_helper.rb:16: warning: default `to_a' will be obsolete
E./Users/bebe/railsapp/test_slip202/app/helpers/application_helper.rb:16: warning: default `to_a' will be obsolete
."[#, #]"
"[#]"
./Users/bebe/railsapp/test_slip202/app/helpers/application_helper.rb:16: warning: default `to_a' will be obsolete
../Users/bebe/railsapp/test_slip202/app/helpers/application_helper.rb:16: warning: default `to_a' will be obsolete
E
Finished in 1.795426 seconds.

  1) Error:
test_should_create_slip(SlipsControllerTest):
ActionView::TemplateError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.index
    In journals/_form.a2

...(中略)...

  2) Error:
test_should_update_slip(SlipsControllerTest):
ActionView::TemplateError: You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.index
    In journals/_form.a2

...(中略)...

7 tests, 8 assertions, 0 failures, 2 errors
rake aborted!
Command failed with status (1): [/System/Library/Frameworks/Ruby.framework/...]

(See full trace by running task with --trace)
  • 自分なりに読み解いてみると、どうやらcreate、updateアクションのところで、予期せずnilが代入されたことでエラーが発生していると理解した。
  • 「1アクションで複数のモデルを保存するテストプロジェクト」のslipsコントローラーのcreateアクションは以下のようになっている。
...(中略)...
  def create
    @slip = Slip.new(params[:slip])
    @journals = @slip.make_journals(params[:journal])
    Slip.transaction do
      @slip.save!
      @journals.each {|journal| journal.save! if journal.input?}
      respond_to do |format|
        flash[:notice] = _('Slip was successfully created.')
        format.html { redirect_to(@slip) }
        format.xml  { render :xml => @slip, :status => :created, :location => @slip }
      end
    end

  rescue
    @journals.each {|journal| journal.valid? if journal.input?}
    respond_to do |format|
      format.html { render :action => "new" }
      format.xml  { render :xml => @slip.errors, :status => :unprocessable_entity }
    end
  end
...(中略)...
  • 3行目でparams[:journal]を取得しようとしているにもかかわらず、テストコードのシミュレートではパラメーターが「slip=>{}」となっている。
  • 原因は、実際にフォームを送信するときのパラメーターとそのハッシュ構造が異なっている為にエラーが発生してしまったようだ。(updateアクションも同様)
  • 以下のようにテストコードを修正することで、正しくシミュレートすることが出来るようになった。
...(中略)...
  def test_should_create_slip
    assert_difference('Slip.count') do
      #post :create, :slip => { }
      post :create, {:slip   =>{:number=>'3', :executed_on=>'2/20', :total_yen=>'3000'}, 
                     :journal=>{"1"=>{:yen=>"3000", :index=>"j943792543", :position=>"1", :comment=>"test3"}}
                    }
    end

    assert_redirected_to slip_path(assigns(:slip))
  end
...(中略)...
  def test_should_update_slip
    #put :update, :id => slips(:one).id, :slip => { }
    put :update, {:id     =>'1', 
                  :slip   =>{:number=>'1', :executed_on=>'2/14', :total_yen=>'10,000'}, 
                  :journal=>{"1"=>{:yen=>"10,000", :index=>"j943792544", :position=>"1", :comment=>"test"}}
                 }
    assert_redirected_to slip_path(assigns(:slip))
  end
...(中略)...
  • テスト結果も正常になったようだ。
$ rake test:functionals
(in /Users/bebe/railsapp/test_slip202)
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby -Ilib:test "/Library/Ruby/Gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader.rb" "test/functional/slips_controller_test.rb" 
Loaded suite /Library/Ruby/Gems/1.8/gems/rake-0.8.1/lib/rake/rake_test_loader
Started
../Users/bebe/railsapp/test_slip202/app/helpers/application_helper.rb:16: warning: default `to_a' will be obsolete
."[#, #]"
"[#]"
./Users/bebe/railsapp/test_slip202/app/helpers/application_helper.rb:16: warning: default `to_a' will be obsolete
...
Finished in 0.541049 seconds.

7 tests, 13 assertions, 0 failures, 0 errors
  • ちなみに、application_helper.rbの16行目の「to_a」が不要かもしれないと警告されているが、コードを確認すると必要な時もありそうなので無視することにした。
# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
  def to_currency
    number_to_currency(self)
  end

  def slip_form_for(record_or_name_or_array, *args, &block)
    options = args.last.is_a?(Hash) ? args.pop : {}
    options.merge!(:builder=>LabelFormWithMsgBuilder)
    args << options

    name = case record_or_name_or_array
           when String, Symbol
            record_or_name_or_array
           else
            ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.to_a.last) ##ここが16行目
           end

    concat('<fieldset>', block.binding)
    concat("<legend>#{name}</legend>", block.binding)
    form_for(record_or_name_or_array, *args, &block)
    concat('</fieldset>', block.binding)
  end
...(中略)...