receive_message_chain和臭代码

时间:2017-08-28 11:56:36

标签: ruby-on-rails rspec

我正在测试我的控制器,并且有一个产生AR查询的字符串: current_user.providers.find(params[:id])。我需要它来准确返回我测试的对象,否则,控制器获得的参考不同于我在规范中的参考,并且某些存根(如allow(provider).to receive(:recreate)不起作用。

我发现这样做的唯一方法就是使用receive_message_chain这样: allow(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

2 个答案:

答案 0 :(得分:0)

通过从链中删除current_user,您的控制器是否也能正常工作? Provider.find(params[:id])这样你就可以使用更少链接的方法和更简单的代码进行测试。我认为current_user.providers.find链不只是Provider.find

答案 1 :(得分: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

等等。通常,您希望使用双打作为最后的解决方案。想一想如何根据实际效果来测试代码,而不是它调用的方法。这适用于任何集成测试(控制器都是)。如果您正在编写单元测试并需要隔离 - 那么就可以通过模拟来实现这种隔离。

相关问题