1時間でツイッターサービスを作ろうにテストを書いてみた2


昨日の続き。
機能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)