使用FactoryGirl和实例变量

时间:2016-05-18 18:59:28

标签: ruby-on-rails rspec controller associations factory-bot

我没有很好地编写测试,并且我在使用Application Controller中的实例变量来测试其他控制器时遇到了一些麻烦。 在Rails中,我有一个非常简单的控制器动作。

  def index
    @cities = City.all
    @starred_cities = @cities.where(starred: true)
  end

对于这个动作,我有一个测试:

RSpec.describe CitiesController, :type => :controller do
  let(:city) { create(:city) }

  describe 'GET #index' do
    let(:cities) { create_list(:city, 2) }
    before { get :index }

    it 'populates an array of all cities' do
      expect(assigns(:cities)).to match_array(cities)
    end

    it 'renders index view' do
      expect(response).to render_template :index
    end
  end
end

在应用程序中,我需要按域名获取国家/地区并为所有控制器设置全局。我像这样添加了ApplicationController before_action方法:

before_action :get_country
def get_country
  country_slugs = {en: 'usa', ru: 'russia', es: 'spain'}
  current_country_slug = country_slugs[I18n.locale]
  @country = Country.find_by_slug(current_country_slug)
end

现在我只能在当前的国家/地区使用我的控制器中的城市:

def index
  @cities = @country.cities
  @starred_cities = @cities.where(starred: true)
end

现在我遇到了一些麻烦,因为我的控制器测试失败了,例外:

Failures:

1) CitiesController GET #index populates an array of all cities
  Failure/Error: @cities = @country.cities

  NoMethodError:
   undefined method `cities' for nil:NilClass
   # ./app/controllers/cities_controller.rb:5:in `index'
   # ./spec/controllers/cities_controller_spec.rb:9:in `block (3 levels) in <top (required)>'

2) CitiesController GET #index renders index view
  Failure/Error: @cities = @country.cities

  NoMethodError:
   undefined method `cities' for nil:NilClass
  # ./app/controllers/cities_controller.rb:5:in `index'
  # ./spec/controllers/cities_controller_spec.rb:9:in `block (3 levels) in <top (required)>'

请帮助,我该怎么做才能组合这样的实例变量并在其上建立关联?

1 个答案:

答案 0 :(得分:0)

您必须正确设置测试用例中使用的所有关联,在您的情况下,缺少已分配城市的国家/地区(因此调用nil.cities)或模拟返回对象的方法,因为AR将返回它们,如:

RSpec.describe CitiesController, :type => :controller do
  describe '#index' do
    let(:cities) { double('cities') }
    let(:starred_cities) { double('starred_cities') }
    let(:country) { double('country', cities: cities) }

    before do
      allow(cities).to receive(:where).with(starred: true).and_return(starred_cities) 
      allow(Country).to receive(:find_by_slug).and_return(country)
      get :index
    end

    it 'populates an array of all cities' do
      expect(assigns(:cities)).to match_array(cities)
    end

    it 'renders index view' do
      expect(response).to render_template :index
    end
  end
end

如果你知道你正在做什么来阻止命中db(慢!),那么模拟可能非常有用,因为AR已经经过了很好的测试。但是也可以让你编写传递测试,虽然你的实现有bug,所以明智地使用它。