昨日の続き。
機能2:button押下時に、twitterへmessageをポストする
のテストを書いてみます。
rochefort's twitter_helloworld at master - GitHub
続・1時間でツイッターサービスを作ろうにテストを書いてみた - うんたらかんたら日記
1時間でツイッターサービスを作ろうにテストを書いてみた - うんたらかんたら日記
まずはcontroller
modelの修正はないので、controllerから考えます。
とりあえず、postするとtwitterへtweetし、
status=200となるようにテストを書いてみます。
#spec/controllers/messages_controller_spec.rb describe "POST 'create'" do context "tweetに成功した場合" do before do controller.stub_chain(:current_user, :twitter, :post).and_return(true) post 'create' end it "リクエストが成功すること" do response.should be_success end end end
エラーとなることを確認します。
$ spec spec/controllers/ F. 1) ActionController::UnknownAction in 'MessagesController POST 'create' tweetに成功した場合 リクエストが成功すること' No action responded to create. Actions: index
routes.rbは修正済みなので
controllerにメソッドを追加します。
#app/controllers/messages_controller.rb def create end
とりあえず、テストは通りました。
flashのテストを追加します。
#spec/controllers/messages_controller_spec.rb describe "POST 'create'" do context "tweetに成功した場合" do before do controller.stub_chain(:current_user, :twitter, :post).with('/statuses/update.json', :status => "偉大なるHelloWorld").and_return(true) post 'create' end it "リクエストが成功すること" do response.should be_success end it "成功メッセージを表示すること" do flash[:success].should == "おめでとう!偉大なるHelloWorldは成功した。" end it "/へ遷移すること" do response.should redirect_to root_path end end end
プロダクトコードを修正。
#app/controllers/messages_controller.rb def create if current_user.twitter.post('/statuses/update.json', :status => "偉大なるHelloWorld") flash[:success] = "おめでとう!偉大なるHelloWorldは成功した。" redirect_to root_path end end
最初の response.should be_success が通らなくなりました。
redirectさせたので、response_code が302になったからですね。
redirectの確認は3つ目のテストで確認できているので
最初のテストは削除します。
あと、it に任せる形に変更しました。
(subject定義しないと、-fs / -fnとかで見た時にわかりにくいな)
#spec/controllers/messages_controller_spec.rb describe "POST 'create'" do context "tweetに成功した場合" do before do controller.stub_chain(:current_user, :twitter, :post).with('/statuses/update.json', :status => "偉大なるHelloWorld").and_return(true) post 'create' end it { flash[:success].should eql "おめでとう!偉大なるHelloWorldは成功した。" } it { response.should redirect_to root_path } end end
$ spec spec/controllers -cfs MessagesController GET 'index' - should be successful MessagesController POST 'create' tweetに成功した場合 - should eql "おめでとう!偉大なるHelloWorldは成功した。" - should redirect to "/" Finished in 0.073295 seconds 3 examples, 0 failures
tweetに失敗した場合のテストも書いてみましょう
#spec/controllers/messages_controller_spec.rb context "tweetに失敗した場合" do before do controller.stub_chain(:current_user, :twitter, :post).with('/statuses/update.json', :status => "偉大なるHelloWorld").and_return(false) post 'create' end it { flash[:error].should eql "残念だが、偉大なるHelloWorldは失敗に終わった。" } it { response.should render_template('index') } it { response.should be_success } end
エラーになることを確認し、実装していきます。
#app/controllers/messages_controller.rb def create if current_user.twitter.post('/statuses/update.json', :status => "偉大なるHelloWorld") flash[:success] = "おめでとう!偉大なるHelloWorldは成功した。" redirect_to root_path else flash[:error] = "残念だが、偉大なるHelloWorldは失敗に終わった。" render :action => 'index' end end
$ spec spec/controllers -cfs MessagesController GET 'index' - should be successful MessagesController POST 'create' tweetに成功した場合 - should eql "おめでとう!偉大なるHelloWorldは成功した。" - should redirect to "/" MessagesController POST 'create' tweetに失敗した場合 - should eql "残念だが、偉大なるHelloWorldは失敗に終わった。" - should render template "index" - should be success Finished in 0.089292 seconds 6 examples, 0 failures
一応、プラグインの処理を分離したcontrollerのテストはできました。
TODO:でも実際にtwitterに投げた後、タイムラインの結果を取得してつぶやいたのが反映されているよね、という確認までしたいけど。どうやんの?
あと、flash[:success] / flash[:erorr] をviewで確認できていません。
ここは、cucumberなんでしょうか。
とりあえず、今回はパスしました。
エラーハンドリング
一見終了のようですが、controllerでエラーハンドリングが十分でないため
post時に例外が発生すると、異常終了してしまいます。
なので、エラーハンドリングのテストを書いてみます。
具体的には、連投するとduplicateエラーとなります。
(どれぐらい過去のデータを見ているかは不明。)
TwitterAuth::Dispatcher::Error in MessagesController#create
Status is a duplicate.
書き方があってるかわかりませんがcontrollerのspecに書いてみました。
#spec/controllers/messages_controller_spec.rb context "tweet時にduplicateエラーによる例外が発生した場合" do before do post_tweet_stub.once.and_raise(TwitterAuth::Dispatcher::Error) post 'create' end it { flash[:error].should eql "連投エラーです。" } it { response.should render_template('index') } it { response.should be_success } end
エラーになることを確認して、実装します。
#app/controllers/messages_controller.rb def create begin res = current_user.twitter.post('/statuses/update.json', :status => "偉大なるHelloWorld") rescue TwitterAuth::Dispatcher::Error => e flash[:error] = "連投エラーです。" return render(:action => 'index') end if res flash[:success] = "おめでとう!偉大なるHelloWorldは成功した。" redirect_to root_path else flash[:error] = "残念だが、偉大なるHelloWorldは失敗に終わった。" render :action => 'index' end end
こんなかんじになりました。
#spec/controllers/messages_controller_spec.rb require 'spec_helper' describe MessagesController do describe "GET 'index'" do it "should be successful" do get 'index' response.should be_success end end describe "POST 'create'" do context "tweetに成功した場合" do before do post_tweet_stub.and_return(true) post 'create' end it { flash[:success].should eql "おめでとう!偉大なるHelloWorldは成功した。" } it { response.should redirect_to root_path } end context "tweetに失敗した場合" do before do post_tweet_stub.and_return(false) post 'create' end it { flash[:error].should eql "残念だが、偉大なるHelloWorldは失敗に終わった。" } it { response.should render_template('index') } it { response.should be_success } end context "tweet時にduplicateエラーによる例外が発生した場合" do before do post_tweet_stub.once.and_raise(TwitterAuth::Dispatcher::Error) post 'create' end it { flash[:error].should eql "連投エラーです。" } it { response.should render_template('index') } it { response.code.should == '500' } end context "tweet時に予期せぬエラーによる例外が発生した場合" do before do post_tweet_stub.once.and_raise(Exception) post 'create' end it { flash[:error].should eql "予期せぬエラーが発生しました。" } it { response.should render_template('index') } it { response.code.should == '500' } end end def post_tweet_stub controller.stub_chain(:current_user, :twitter, :post).with('/statuses/update.json', :status => "偉大なるHelloWorld") end end
#app/controllers/messages_controller.rb def create begin res = current_user.twitter.post('/statuses/update.json', :status => "偉大なるHelloWorld") rescue TwitterAuth::Dispatcher::Error => e flash[:error] = "連投エラーです。" return render(:action => 'index', :status => '500') rescue Exception => e flash[:error] = "予期せぬエラーが発生しました。" return render(:action => 'index', :status => '500') end if res flash[:success] = "おめでとう!偉大なるHelloWorldは成功した。" redirect_to root_path else flash[:error] = "残念だが、偉大なるHelloWorldは失敗に終わった。" render :action => 'index' end end
$ spec spec/controllers/ -cfn MessagesController GET 'index' should be successful POST 'create' tweetに成功した場合 should eql "おめでとう!偉大なるHelloWorldは成功した。" should redirect to "/" tweetに失敗した場合 should eql "残念だが、偉大なるHelloWorldは失敗に終わった。" should render template "index" should be success tweet時にduplicateエラーによる例外が発生した場合 should eql "連投エラーです。" should render template "index" should == "500" tweet時に予期せぬエラーによる例外が発生した場合 should eql "予期せぬエラーが発生しました。" should render template "index" should == "500"
追記
だらだら長いけど。
stub_chain が便利。
controllerで下記のようなメソッドを使用していて
current_user.twitter.post
これをスタブ化する場合、こんな風に書ける。
controller.stub_chain(:current_user, :twitter, :post).and_return(true)