在控制器中创建记录后,Rspec测试属性

时间:2017-03-16 12:05:06

标签: ruby-on-rails ruby rspec

背景:我的控制器中有一个after_action回调,它接受字符串address,处理它并将longitudelatitude存储在相应的字段中。我想测试一下。

This SO question,以及this article只考虑更新方法,但至少,它们非常清楚,因为我已经有了一个可以使用的对象。

所以我的问题是 - 如何找到这个新创建的记录? This SO question让我看到了这段代码:

require 'rails_helper'

RSpec.describe Admin::Settings::GeneralSettingsController, type: :controller do

  context "POST methods" do
    describe "#edit and #create" do
      it "encodes and stores lang/lot correctly" do
          post :create, general_setting: FactoryGirl.attributes_for(:general_setting)
          expect(assigns(:general_setting).long).to eq(37.568021)
          # expect(general_setting.long).to eq(37.568021)
          # expect(general_setting.lat).to eq(55.805553)
      end
    end
  end
end

但是使用答案中的代码,我收到了这个错误:

Failure/Error: expect(assigns(:general_setting).long).to eq(37.568021)

     NoMethodError:
       undefined method `long' for nil:NilClass

更新#1: 这是我的新控制器规范代码:

RSpec.describe Admin::Settings::GeneralSettingsController, type: :controller do

  context 'POST methods' do
    before(:each) do
      allow(subject).to receive(:set_long_lat)
    end

    describe 'post create' do
      before(:each) do
        post :create, params: { general_setting: FactoryGirl.attributes_for(:general_setting) }
      end

      it "saves the record with valid attributes" do
        expect{subject}.to change{GeneralSetting.count}.by(1)
      end

      it 'calls :set_long_lat' do
        expect(subject).to have_received(:set_long_lat)
      end
    end
  end

  describe '#set_long_lat' do
    # spec for method
  end
end

更新#2:

这是我的控制器代码:

class Admin::Settings::GeneralSettingsController < AdminController
  include CrudConcern

  before_action :find_general_setting, only: [:edit, :destroy, :update, :set_long_lat]
  after_action :set_long_lat

  def index
    @general_settings = GeneralSetting.all
  end

  def new
    @general_setting = GeneralSetting.new
    # Билдим для того, что бы было видно сразу одно поле и пользователь не должен
    # кликать на "добавить телефон"
    @general_setting.phones.build
    @general_setting.opening_hours.build
  end

  def edit
    # Тоже самое, что и с нью - если телефонов нет вообще, то показываем одно пустое поле
    if @general_setting.phones.blank?
      @general_setting.phones.build
    end

    if @general_setting.opening_hours.blank?
      @general_setting.opening_hours.build
    end
  end

  def create
    @general_setting = GeneralSetting.new(general_setting_params)
    create_helper(@general_setting, "edit_admin_settings_general_setting_path")
  end

  def destroy
    destroy_helper(@general_setting, "admin_settings_general_settings_path")
  end

  def update
    # debug
    # @general_setting.update(language: create_hash(params[:general_setting][:language]))
    @general_setting.language = create_hash(params[:general_setting][:language])
    update_helper(@general_setting, "edit_admin_settings_general_setting_path", general_setting_params)
  end


  private

  def set_long_lat
    geocoder = Geocoder.new
    data = geocoder.encode!(@general_setting.address)
    @general_setting.update!(long: data[0], lat: data[1])
  end

  def find_general_setting
    @general_setting = GeneralSetting.find(params[:id])
  end

  def general_setting_params
    params.require(:general_setting).permit(GeneralSetting.attribute_names.map(&:to_sym).push(
      phones_attributes: [:id, :value, :_destroy, :general_setting_id ]).push(
      opening_hours_attributes: [:id, :title, :value, :_destroy, :deneral_setting_id]) )
  end

  def create_hash(params)
    language_hash = Hash.new

    params.each do |param|
      language_hash[param.to_sym] = param.to_sym
    end
    return language_hash
  end
end

(如果它有帮助 - 我有很多类似的粗暴行为,这就是为什么我把它们全部放在一个关注控制器中)

module CrudConcern 
  extend ActiveSupport::Concern
  include Language

  included do
    helper_method :create_helper, :update_helper, :destroy_helper, :get_locales
  end

  def get_locales
    @remaining_locales = Language.get_remaining_locales
  end

  def create_helper(object, path)
    if object.save!
      respond_to do |format|
        format.html {
          redirect_to send(path, object)
          flash[:primary] = "Well done!"
        }
      end
    else
      render :new
      flash[:danger] = "Something not quite right"
    end
    @remaining_locales = Language.get_remaining_locales
  end

  def update_helper(object, path, params)
    if object.update!(params)
      respond_to do |format|
        format.html {
          redirect_to send(path, object)
          flash[:primary] = "Well done!"
        }
      end
    else
      render :edit
      flash[:danger] = "Something's not quite right"
    end
  end

  def destroy_helper(object, path)
    if object.destroy
      respond_to do |format|
        format.html {
          redirect_to send(path)
          flash[:primary] = "Well done"    
        }
      end
    else
      render :index
      flash[:danger] = "Something's not quite right"
    end
  end

end

更新#3 它不是理想的解决方案,但不知何故,控制器测试不起作用。我已将回调移到模型中并更新了我的general_setting_spec测试。

class GeneralSetting < ApplicationRecord
  after_save :set_long_lat    
  validates :url, presence: true

  private

  def set_long_lat
    geocoder = Geocoder.new
    data = geocoder.encode(self.address)
    self.update_column(:long, data[0])
    self.update_column(:lat, data[1])
  end
end

我的测试现在:

RSpec.describe GeneralSetting, type: :model do

  let (:regular) { FactoryGirl.build(:general_setting) } 

  describe "checking other validations" do
    it "is invalid with no url" do
      expect{
        invalid.save
      }.not_to change(GeneralSetting, :count)
    end

    it 'autofills the longitude' do
      expect{ regular.save }.to change{ regular.long }.from(nil).to(37.568021)
    end

    it 'autofills the latitude' do
      expect{ regular.save }.to change{ regular.lat }.from(nil).to(55.805078)
    end
  end
end

1 个答案:

答案 0 :(得分:0)

我会测试期望控制器调用after_action中指定的方法并对该方法进行单独的测试。

类似的东西:

context 'POST methods' do
  before(:each) do
    allow(subject).to receive(:method_from_callback)
  end

  describe 'post create' do
    before(:each) do
      post :create, params: { general_setting: attributes_for(:general_setting) }
    end

    it 'calls :method_from_callback' do
      expect(subject).to have_received(:method_from_callback)
    end
  end
end

describe '#method_from_callback' do
  # spec for method
end

请务必使用您的方法名而不是:method_from_callback注意我已使用rspec 3.5语法(将请求请求参数包装到params中)。