2013-07-02 42 views
1

我是Ruby和RSpec的新手。我來自Java背景,這就是爲什麼我的測試看起來像junit代碼。我想了解更多關於RSpec的知識,但我不太瞭解subjectlet!let。所以,如果有人能指導我清理這些代碼,我會非常感激。如何以rspec方式改進此RSpec代碼?

我有sinatra,RSpec它正在用Twitter登錄。

get '/login/twitter' do 
    begin 
    request_token = TwitterService.new.authentication_request_token 

    session[:request_token_twitter] = request_token 

    redirect request_token.authorize_url 
    rescue Exception => e 
    logger.error(e.message) 
    redirect '/' 
    end 
end 

get '/login/twitter/success' do 
    request_token = session[:request_token_twitter] 
    twitter_service = TwitterService.new 
    access_token = twitter_service.authorize(request_token, params[:oauth_verifier]) 

    begin 
    twitter_user_info = twitter_service.verify_credentials 

    twitter_id = twitter_user_info["id"] 
    response.set_cookie("auth_token", :value => twitter_id, :path => '/') 
    response.set_cookie(@social_flag, :value => "t", :path => '/') 

    expected_user = @user_manager.find_by_id(twitter_id.to_s) 

    if expected_user.is_null? 
     twitter_user = User.new(twitter_id, access_token.token, access_token.secret, "t") 
     twitter_user.save 

     logger.info("Saving ...") 
     logger.info("Twitter ID #{twitter_id}") 

     redirect '/signup' 
    else 
     expected_user.token = access_token.token 
     expected_user.secret = access_token.secret 
     expected_user.update 

     logger.info("Updating token and secret ...") 
     logger.info("Twitter ID #{twitter_id}") 
    end 

    rescue Exception => e 
    logger.error(e.message) 
    logger.error("There's something wrong with Twitter and user cannot log in") 
    redirect '/' 
    end 

    redirect '/t' 
end 

而這裏是我的RSpec。我知道這真的很難看。

describe "Twitter route" do 
    include TwitterOAuth 

    def app 
     Sinatra::Application 
    end 

    context "/login/twitter" do 
     it "should redirect to twitter authorized url" do 
      request_token = OpenStruct.new 
      request_token.authorize_url = "http://api.twitter.com/oauth/authenticate?oauth_token" 

      TwitterService.any_instance.stub(:authentication_request_token).and_return(request_token) 

      get '/login/twitter' 
      last_response.header["Location"].should include "http://api.twitter.com/oauth/authenticate?oauth_token" 
      last_response.status.should eql 302 
      session[:request_token_twitter].authorize_url.should == "http://api.twitter.com/oauth/authenticate?oauth_token" 
     end 

     it "should redirect back to home page if error occurs" do 
      TwitterService.any_instance.stub(:authentication_request_token).and_raise("Unauthorized") 

      get '/login/twitter' 

      last_response.header["Location"].should include "http://example.org/" 
      last_response.status.should eql 302 
      session[:request_token_twitter].should eql nil 
     end 

     it "should save a user after a success callback from twitter" do 
      user_manager = UserManager.new 

      access_token = OpenStruct.new 
      access_token.token = "token" 
      access_token.secret = "secret" 

      TwitterService.any_instance.stub(:authorize).with(anything(), anything()).and_return(access_token) 
      TwitterService.any_instance.stub(:verify_credentials).and_return({"id" => "id1"}) 

      get '/login/twitter/success' 

      last_response.header["Location"].should include "/signup" 
      rack_mock_session.cookie_jar["auth_token"].should eql "id1" 
      rack_mock_session.cookie_jar["s_flag"].should eql "t" 
      last_response.status.should eql 302 

      user_manager = UserManager.new 
      expected_user = user_manager.find_by_id("id1") 
      expected_user.id.should eql "id1" 
      expected_user.token.should eql "token" 
      expected_user.secret.should eql "secret" 
     end 

     it "should update user token and secret if the user already exists" do 
      User.new("id1", "token", "secret", "t").save 

      access_token = OpenStruct.new 
      access_token.token = "token1" 
      access_token.secret = "secret1" 

      TwitterService.any_instance.stub(:authorize).with(anything(), anything()).and_return(access_token) 
      TwitterService.any_instance.stub(:verify_credentials).and_return({"id" => "id1"}) 

      get '/login/twitter/success' 

      last_response.header["Location"].should include "/t" 
      rack_mock_session.cookie_jar["auth_token"].should eql "id1" 
      rack_mock_session.cookie_jar["s_flag"].should eql "t" 
      last_response.status.should eql 302 

      user_manager = UserManager.new 
      expected_user = user_manager.find_by_id("id1") 
      expected_user.id.should eql "id1" 
      expected_user.token.should eql "token1" 
      expected_user.secret.should eql "secret1" 
     end 

     it "should redirect back to the home page" do 
      access_token = OpenStruct.new 
      access_token.token = "token1" 
      access_token.secret = "secret1" 

      TwitterService.any_instance.stub(:authorize).with(anything(), anything()).and_return(access_token) 
      TwitterService.any_instance.stub(:verify_credentials).and_raise 

      get '/login/twitter/success' 

      last_response.header["Location"].should include "http://example.org/" 
      end 

     end 
end 

任何改進我會感激不只是代碼。可能是我錯過了一些明顯的事情。

非常感謝。

回答

7

好的,這裏發生了很多!

首先,您應該嘗試堅持每個示例一個測試。你的例子目前正在測試一大堆行爲,這意味着你的測試是全有或全無的,並且可能會讓你不清楚如果你破壞了某些東西,具體的破壞是什麼。

首先,我要添加一個新的匹配器。你通常會把它放在spec/support/matchers.rb之類的地方。它只是要擴展rspec的,這樣我們可以測試的響應是一個重定向,並且重定向去一個給定的位置:

RSpec::Matchers.define :redirect_to do |expected| 
    match do |actual| 
    actual.should be_redirect 
    actual.location.should include expected 
    end 
end 

現在,到代碼!

未加說明的來源是在這裏:https://gist.github.com/cheald/5908093 - 這可能會是那麼討厭閱讀:)

let定義將在每一個例子剛好運行一次,不管它有多少次調用的方法。這讓我們有一個在例子時間定義的「變量」,這讓我們可以在嵌套示例中重寫它。在這裏,我已經定義了access_token頂部,但是我們將在另一個更深的示例中使用let另一個access_token。這個套件並沒有真正表現出來,但是這可以讓你在另一個引用let的東西時做出好的事情。試想一下,如果你願意,我們有

let(:user) { user_manager.find(access_token.id) } 

這將使用最深的嵌套user_manager和最深的嵌套的access_token,而無需在每個嵌套的範圍內重新聲明用戶。便利!

let塊不被調用他們正在使用,直到(與let!塊,申報時總是調用)

describe "Twitter route" do 
    include TwitterOAuth 

    let(:app) { Sinatra::Application } 
    let(:request_token) { double("request_token", authorize_url: "http://api.twitter.com/oauth/authenticate?oauth_token") } 
    let(:access_token) { double("token", token: "token", secret: "secret") } 
    let(:user_manager) { UserManager.new } 

你會發現,我打破了你的測試成嵌套上下文來分類相似的行爲。也就是說,應該通過授權令牌傳遞的所有測試都嵌套在授權令牌上下文中,我們的before塊設置上下文,以便此上下文中的所有示例都獲得有效的令牌。

我們也在前面的塊中做get,所以我們可以直接測試結果。

context "/login/twitter" do 
    context "with an authorized token" do 
     before do 
     TwitterService.any_instance.stub(:authentication_request_token).and_return(request_token) 
     TwitterService.any_instance.stub(:authorize).with(anything(), anything()).and_return(access_token) 
     TwitterService.any_instance.stub(:verify_credentials).and_return({"id" => "id1"}) 
     get '/login/twitter' 
     end 

你在這裏看到我正在使用我們的新匹配器。它讓我們在一次測試中檢查重定向到給定的URL。

 it "should redirect to twitter authorized url" do 
     last_response.should redirect_to "http://api.twitter.com/oauth/authenticate?oauth_token" 
     end 

     it "should set a the request token in the session" do 
     session[:request_token_twitter].authorize_url.should == "http://api.twitter.com/oauth/authenticate?oauth_token" 
     end 

     context "after a success callback" do 
     let(:user) { user_manager.find_by_id("id1") } 
     context "when there is not an existing user" do 
      before do 
      get '/login/twitter/success' 
      end 

      it "should redirect to /signup" do 
      last_response.should redirect_to "/signup" 
      end 

      it "should set an auth_token cookie" do 
      rack_mock_session.cookie_jar["auth_token"].should == "id1" 
      end 

      it "should set an s_flag cookie" do 
      rack_mock_session.cookie_jar["s_flag"].should == "t" 
      end 

在這裏你會看到subject。它只是定義變量subject返回的內容,並使its塊在其上運行。在這種情況下,主題是User記錄。由於subject是用戶記錄,因此我可以使用更簡潔的表單來檢查其屬性。

  context "the authenticated user" do 
      subject { user } 
      its(:id)  { should == "id1" } 
      its(:token) { should == "token" } 
      its(:secret) { should == "secret" } 
      end 
     end 

您會在這裏看到我爲access_token提供了一個新的定義。當這些例子運行時,在頂部的前面塊(設置「授權令牌」)將使用這個access_token而不是上面定義的那個方式。這讓我們可以覆蓋用於使用特定於上下文的變量設置上下文的變量。

 context "when there is an existing user" do 
      let(:access_token) { double("token", token: "newtoken", secret: "newsecret") } 
      before do 
      User.new("id1", "oldtoken", "oldsecret", "t").save 
      get '/login/twitter/success' 
      end 

      it "should set an auth_token cookie" do 
      rack_mock_session.cookie_jar["auth_token"].should == "id1" 
      end 

      it "should set an s_flag cookie" do 
      rack_mock_session.cookie_jar["s_flag"].should == "t" 
      end 

      it "should redirect to /t" do 
      last_response.should redirect_to "/t" 
      end 

      context "the authenticated user" do 
      subject { user } 
      its(:id)  { should == "id1" } 
      its(:token) { should == "newtoken" } 
      its(:secret) { should == "newsecret" } 
      end 
     end 
     end 
    end 

    context "with an invalid token" do 
     before do 
     TwitterService.any_instance.stub(:authentication_request_token).and_raise("Unauthorized") 
     get '/login/twitter' 
     end 

     it "should redirect back to home page if error occurs" do 
     last_response.should redirect_to "http://example.org/" 
     end 

     it "should not set a session value" do 
     session[:request_token_twitter].should be_nil 
     end 
    end 
    end 
end 
+0

哇非常感謝! – toy

+0

嘗試創建某些Cookie存在的環境正在融化我的大腦。非常感謝克里斯! – Starkers