flash.nowの内容を直接テストすることは不可能
目指す例外ActiveRecord::StaleObjectErrorの発生を確認できたので、その状況を適切に表示するようにしたい。またしてもテストを先に書いてみる。そして、またしてもハマった...。
テスト
- 欲しいメッセージが、flash[:notice]に入っていれば良いと考えた。
# functionalテスト: test/functional/slips_controller_test.rb ...(中略)... class SlipsControllerTest < ActionController::TestCase def test_shoould_not_update_slip_when_slip_in_lock 2.times do put :update, {:id =>'1', :slip =>{:number=>'1', :executed_on=>'2/14', :total_yen=>'1,000', :lock_version=>'0'}, :journal=>{"1"=>{:yen=>"1,000", :index=>"j943792544", :position=>"1", :comment=>"test"} } } end assert_response :success assert_template 'edit' assert_equal "他のユーザーが編集済のため修正できません。", flash[:notice] ###### 追加した行 end ...(中略)...
実装
- rescueの中で、例外によって処理を分岐してみた。
# コントローラー: app/controllers/slips_controller.rb class SlipsController < ApplicationController ...(中略)... def update @slip = Slip.find(params[:id]) @journals = @slip.make_journals(params[:journal]) Slip.transaction do @slip.update_attributes!(params[:slip]) # 保存前にDBのjournalを一旦クリアする。journalは毎回、新規作成される。クリアしないと二重登録になってしまう。 @slip.journals.clear @journals.each {|journal| journal.save! if journal.input?} respond_to do |format| flash[:notice] = _('Slip was successfully updated.') format.html { redirect_to(@slip) } format.xml { head :ok } end end rescue => error_obj case error_obj when ActiveRecord::StaleObjectError flash.now[:notice] = "他のユーザーが編集済のため修正できません。" else @journals.each {|journal| journal.valid? if journal.input?} end respond_to do |format| format.html { render :action => "edit" } format.xml { render :xml => @slip.errors, :status => :unprocessable_entity } end end ...(中略)...
問題
1) Failure: test_shoould_not_update_slip_when_slip_in_lock(SlipsControllerTest) [./test/functional/slips_controller_test.rb:96:in `test_shoould_not_update_slip_when_slip_in_lock' /Library/Ruby/Gems/1.8/gems/activesupport-2.0.2/lib/active_support/testing/default.rb:7:in `run']: --- /var/folders/7t/7txMamB3F5yDjLEWm5P6ik+++TI/-Tmp-/expect.2983.0 2008-04-10 14:14:07.000000000 +0900 +++ /var/folders/7t/7txMamB3F5yDjLEWm5P6ik+++TI/-Tmp-/butwas.2983.0 2008-04-10 14:14:07.000000000 +0900 @@ -1 +1 @@ -他のユーザーが編集済のため修正できません。 +
原因
- 原因は、「flash.now」にあった。
- あれれ、「assert_equal "他のユーザーが...", flash[:notice]」を実行する時点で、flash[:notice]は破棄されている可能性が...。
- put :updateは処理を現場に投げて、「assert_response :success」「assert_template "edit"」が通っているので。
まさにその通りであった...。
解決
- ということで、「assert_equal "他のユーザーが...", flash[:notice]」をテストすることは不可能。
- でも、flash[:notice]はメッセージとして必ず描画されるので、ビューの中に目指すメッセージが含まれていればそれでOKなのだ。
- というより、メッセージが表示されているかまで確認することの方が重要だ。以下のように修正してみた。
# functionalテスト: test/functional/slips_controller_test.rb ...(中略)... class SlipsControllerTest < ActionController::TestCase def test_shoould_not_update_slip_when_slip_in_lock 2.times do put :update, {:id =>'1', :slip =>{:number=>'1', :executed_on=>'2/14', :total_yen=>'1,000', :lock_version=>'0'}, :journal=>{"1"=>{:yen=>"1,000", :index=>"j943792544", :position=>"1", :comment=>"test"} } } end assert_response :success assert_template 'edit' # assert_equal "他のユーザーが編集済のため修正できません。", flash[:notice] assert_select "p", "他のユーザーが編集済のため修正できません。" ###### 追加した行 end ...(中略)...
所感
これでテストは通った!それにしても、実装は簡単なのに、正しいテストを書けないためにそこでハマってしまう、その繰り返しだ。これでは何のためのテストなんだか...。でも、前向きに考えれば、以下のような効果も感じている。
- 目指す機能を実装する前に、自分としては相当、論理的に考えるようになった。
- 以前は欲しい機能を適当に想像しながら、途中で試行錯誤することが多かった。(今はテストで試行錯誤しているが...。)
- 先にテストを書くには、目指す機能が明確である必要がある。その機能について、仕組みをよく考えるようになった。
- Railsの仕様に少し詳しくなった気がする。
- テストは、「assert_XXXX ...」つまり、「... であることに間違いない。」という真実を積み上げていくので、曖昧に理解していたことが真実として明らかになっていく気がする。
- 修正した結果に自信が持てる。(一番のメリットかも。過去のテストもすべて通っているという安心感。)
- テストは一つの機能を別のアプローチから表現しているようなもの。
- 実装したコードは、設計図として内部の仕組みを表現している。
- テストコードは、内部の仕組みはブラックボックスで、マニュアルとしてその使い方を表現している。
とりあえず、もう少し続けてみる。テストに慣れる必要がある。