背景:我的控制器中有一个after_action
回调,它接受字符串address
,处理它并将longitude
和latitude
存储在相应的字段中。我想测试一下。
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
答案 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
中)。