2013-07-02 5 views
1

Я новичок в Ruby и RSpec. Я пришел из фона Java, поэтому мой тест действительно выглядит как код юнита. Я пытаюсь узнать больше о RSpec, но я не совсем понимаю subject, let, !let. Поэтому, если кто-нибудь может помочь мне очистить этот код, я был бы очень благодарен.Как улучшить этот код RSpec в rspec?

У меня есть синатра, 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 без переобъявить пользователь в каждой вложенной области. Handy!

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

Wow Большое спасибо! – toy

+0

Пытаясь создать контексты, где существовали определенные куки, таял мой мозг. Большое спасибо Крису! – Starkers

Смежные вопросы