2017-08-28 86 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