2017-08-28 61 views
0

我正在測試我的控制器,並且有一個產生AR查詢的字符串: current_user.providers.find(params[:id])。我需要它返回到我測試的對象,否則,控制器獲取的參考信息與我在spec中的參考信息不同,並且某些存根(如allow(provider).to receive(:recreate))不起作用。receive_message_chain和臭味代碼

我發現要做到這一點的唯一方法是使用這樣的receive_message_chainallow(provider.user).to receive_message_chain(:providers, :find => provider)。但是rspec文檔says考慮使用receive_message_chain作爲有異味的代碼。 另外,我想後面我可能需要用另一個ID調用current_user.providers.find(otherid)來獲得另一個對象,這樣就不再適合我了。

有什麼辦法可以做得更好嗎?我已經設法避免allow_any_instance_of,這也被認爲是臭,所以我相信有一種方法可以避免這一點,我只是看不到它。 如果沒有,我至少想知道是否有什麼方法可以將with添加到receive_message_chain

===========

我只是想測試我控制器的方法update

# app/controllers/restream/facebooks_controller.rb 
class Restream::FacebooksController < Restream::BaseController 
    def update 
    current_user.providers.find(params[:id]) 

    if @fb.update_attributes(facebook_params) 
     if event_changed? 
     @fb.recreate 
     else 
     @fb.update 
     end 
     redirect_to restreams_path 
    else 
     render 'edit' 
    end 
    end 
end 

#spec/controllers/restream/facebooks_controller_spec.rb 

require 'rails_helper' 

describe Restream::FacebooksController do 
    let!(:facebook) { create(:restream_facebook) } 
    let!(:restream) { facebook.restream } 

    before do 
    login(restream.user) 
    end 

    describe '#update' do 
    let!(:params_hash) { { 
     :title   => facebook.title, 
     :privacy  => facebook.privacy, 
     :destination => facebook.destination, 
     :destination_id => facebook.destination_id, 
     :description => facebook.description 
     } } 
    let!(:request_hash) { { 
      :restream_facebook => params_hash, 
      :id     => facebook.id 
     } } 

    before do 
     allow(facebook.user). 
     to receive_message_chain(:providers, :find => facebook) 
     allow(facebook).to receive(:update) 
     allow(facebook).to receive(:recreate) 
    end 

    context 'updates' do 
     it 'title' do 
     params_hash[:title] = SecureRandom.hex(2) 
     post :update, request_hash 

     expect(facebook.reload.title).to eq params_hash[:title] 
     end 
    end 
    end 
end 

回答

0

您的控制器的工作原理是從鏈中刪除current_userProvider.find(params[:id])這樣你就可以獲得更少的鏈接方法和更簡單的代碼來測試。我不認爲current_user.providers.find的連鎖店做的不僅僅是Provider.find

+0

我無法永久刪除'current_user'。而且我不想擁有一個像'if Rails.env.test?'這樣的字符串。或者你的意思是我可以用其他方式做到嗎? – Ngoral

+0

您在文章的第一句中引用的控制器方法,我會建議用'Provider.find(params [:id])替換它,因爲它應該像'current_user'一樣工作。如果它爲你產生相同的行爲,那麼你的測試代碼看起來好像更容易運行,因爲方法鏈較少。這可能有助於編輯您的帖子更多的代碼示例,因爲我可以在我的假設中脫穎而出。 – abax

+0

我可以輕鬆地添加更多的代碼示例,只是不知道究竟是什麼。 我試過使用'allow(Provider).to接收(:find).with(provider.id).and_return provider',但由於某種原因,這並不起作用。 – Ngoral

0

receive_messaged_chain可以是一種氣味,但使用雙打時測試控制器是更大的臭味。

既然你沒有提供任何控制器動作的代碼,我會給你常見的例子:

def destroy 
    @provider = current_user.providers.find(params[:id]) 
    @provider.delete 
end 

,不測試,如果delete方法被調用。測試物體是否從DB中消失,例如:

let(:current_user) { FactoryGirl.create(:user) } 
let!(:provider) { FactoryGirl.create(:provider, user: current_user } 
it do 
    delete :destroy, id: provider.id 
    expect(Provider.find(provider.id).to raise_error(ActiveRecord::RecordNotFound) # writing from memory, don't remember exactly how the exception is called 
end 

# or 

it do 
    expect { delete :destroy } 
    .to change{ Provider.where(id: provider.id).count }.from(1).to(0) 
end 

依此類推。一般來說,你想使用雙打作爲最後的解決方案。想想按照它所做的實際效果來測試代碼的方法,而不是它所調用的方法。這適用於任何集成測試(哪些控制器是)。如果你正在編寫單元測試並需要隔離 - 那麼就模擬一些東西來實現這種隔離。

+0

事實上,我正在測試'update'方法,並希望確保對我需要的對象進行更新。我現在會提供一些代碼。但我想我只是意識到我可以只測試不是'facebook。title',但是'Restream :: Facebook.find(facebook.id).title'。我猜是這樣。 – Ngoral

+0

我也意識到,我絕對想要存根(或者我可以稱之爲錯誤,我仍然對這些單詞不熟悉)'''初始化'和'重新創建'方法。如果我不會'替換'provider.find'的結果,那麼我需要使用'allow_any_instance_of'來存儲這些方法。所以我仍然需要用我需要的東西來代替它所找到的東西。 – Ngoral

+0

@Ngoral在這種情況下,你是對的。 Stub Finder方法(Something.find或某些東西,provider.find和receive_message_chain)返回您在測試中設置的模擬,並檢查該模擬是否調用了某種方法。我認爲只要你清楚你正在測試什麼,在這裏使用'receive_message_chain'就不是什麼大問題。 – meta