コントローラーをテストしてみる
コントローラーのテストも、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 ...(中略)...