我在 rspec 上面临着一个巨大的问题,每次我尝试解决一个错误时,它都会创建另一个错误。
我有一个模型交易,其中有_许多步骤
模型/步骤.rb
belongs_to :deal, :foreign_key => 'deal_id'
模型/deal.rb
has_many :steps, dependent: :destroy do
# added to enable maximum nb of steps a deal can have
# to take into account in active admin
# in order to calculate the correct new nb of step to compare to the authorized limit
# source: homeonrails.com/2012/10/validating-nested-associations-in-rails/
def length
reject(&:marked_for_destruction?).length
end
end
我所有错误的要点是如何使我的功能 rspec 测试之一工作,其中我将交易和步骤关联起来。我曾经使用 Factory Girl“通常”瞬态,这会更干净,但我必须放弃它(请参阅下文),因为我们有特殊要求:
factory :deal_with_associated_steps do
to_create {|instance| instance.save(validate: false) } # skip validate
transient do
steps_count 27
end
after(:create) do |deal, evaluator|
create_list(:steps, evaluator.steps_count, deal: deal)
end
end
我不再使用这种“瞬态”技术来创建与交易相关的多个步骤的原因与我们的应用程序非常相关。
当您获取给定 Deal 的所有关联 Steps 的 st_appearance_order_nb (整数)时,它必须始终是:
- 从0开始
- 然后没有间隙,所以 1, 2,3...
Deal 模型 Deals 上的一些 before_validations 让我能够确保始终如此。您不能与关联步骤进行交易,其中一个步骤的外观_nb 为 1,另一个步骤的外观_nb 为 3,但不存在外观_nb 为 2 的步骤。并且您不能拥有外观_nb 为 2 的步骤。 0. 它必须是一个系列 0,1,2,3...等等
实际上,这仍然适用于在 Factory Girl 中创建 Steps 的“经典瞬态”方式。但是上面创建步骤的经典“瞬态”方式的问题是我有一个称为“rspec-重试”的宝石 https://github.com/NoRedInk/rspec-retry这可以帮助我重新进行功能测试,就像许多其他具有复杂 UI/javascript 页面的 rspec 用户一样,有时我的前端测试第一次会因为某些 js/加载原因而失败,然后如果您重复足够多的次数,则第二次或第三次它会起作用的。所以我的大多数功能测试都运行了 4 次,有些只通过了第二次或第三次:) gem rspec-retry 很简洁,但与我在 Factory Girl 中创建相关步骤的“瞬态”方式有一个非常重大的问题:
我收到错误,因为如果测试第一次“重试”失败,第二次,就像测试应用程序认为 st_appearance_order_nb nb 0 到 4 已经被采用(实际上它们是由第一个 rspec“尝试”创建的),所以现在它创建了 4 个新步骤,st_appearance_order_nb 分别为 5、6、7 和 8。
然后...导致错误,因为我有一个 before_validation 来确保交易相关步骤的 st_appearance_order_nb 始终从 0 开始,然后一一递增
因此,使用 rspec-retry,我无法使用 Factory Girl 瞬态方式创建关联的步骤,至少这是我当时找到另一种方法时的结论:我决定以这种方式“手动”创建关联的步骤
let!(:deal_with_videos) { create(:deal,
title: "title deal 1" ) }
video_urls = [ "", # no video allowed on first step
"https://www.facebook.com/418234631906607/videos/495053617558041", # square video (5 sec)
"https://www.facebook.com/pili.morillo.56/videos/352355988613922", # landscape video with internal black sidebars
"https://www.facebook.com/rihanna/videos/10155330511896676/", # landscape video no internal black sidebars
"https://www.facebook.com/videos/1592424637461205/", # portrait video
""
]
(0..5).each do |n|
let!(:"deal_with_videos_step#{n}") {
FactoryGirl.create(:step,
st_appearance_order_nb: n,
st_video_url: video_urls[n],
deal: deal_with_videos)
}
end
这修复了错误,我的 99% 的测试都有效,但现在这篇文章的问题是:我的一个测试失败了:因为非常奇怪的是,我关联 Deal 和 Steps 的方式并不完全地工作,但只是部分......让我补充一下,在生产和开发模式下一切都工作正常。
describe "on Deal Page load, the view behaves appropriately in terms of video" do
let(:action) { visit actual_deal_page_path(deal_with_videos) }
let(:fb_player_visibility) { "hidden" }
let(:video_href_set_by_app_js) { nil.to_s }
it_behaves_like "a view where the FB video player behaves appropriately"To be clear I found a very hack way to do stuff, but it created another issue in a ripple effect a new bug as the way I was doing it was making the test suite think there was no new
测试失败的原因我现在知道:它失败了因为下面的 before_validation 的内容永远不会被执行
models/deal.rb
before_validation :extract_st_embed_hostings_from_st_video_urls
def extract_st_embed_hostings_from_st_video_urls
puts "beacon1"
self.steps.reject(&:marked_for_destruction?).each do |step|
puts "beacon2"
# do stuff
end
end
我知道,因为这是测试环境中带有这些 put 消息的问题,当我在测试块上运行 rspec test 时,我只看到“beacon1”,而不是 beacon2 (在 dev 和 prod 中都看到这两条消息)
我想知道为什么它没有被执行。
所以我在测试中添加了一些puts
看看为什么这条线self.steps.reject(&:marked_for_destruction?).each do |step|
没有输出任何东西。我的交易和步骤关联在测试中是否不起作用?
describe "on Deal Page load, the iew behaves appropriately in terms of video" do
before do
action
puts deal_with_videos.steps.to_json
puts deal_with_videos.steps[1].to_json
puts deal_with_videos.id
puts deal_with_videos_step0.deal_id
puts deal_with_videos_step0.deal.title
puts deal_with_videos_step0.to_json
end
let(:action) { visit actual_deal_page_path(deal_with_videos) }
let(:fb_player_visibility) { "hidden" }
it_behaves_like "a view where the FB video player behaves appropriately"
end
结果很奇怪:
-
“puts deal_with_videos.steps.to_json”给了我 [] => 所以看来它们没有关联
“puts deal_with_videos.id”给出 3
“puts deal_with_videos_step0.deal_id”也给我 3
因此,在两个方向上,我得到了相同的信息,这很奇怪:看起来它们实际上是密切相关的。很奇怪,因为它对我来说与前两个看跌期权相矛盾。
放置 deal_with_videos_step0.deal.title 给中小企业“标题 deal1”
put deal_with_videos_step0.to_json 为我提供了一个详细的 json,其中包含内容(为了保持简洁,此处未复制)
=> 他们都工作
我的结论
就像我将它们联系起来的方式只有一种方式:
如果我从像 deal_with_videos_step0 这样的步骤开始,然后使用 .deal 移动到 Deal 表,它就会起作用。
但反过来说,我的 before_validation 中名为 extract_st_embed_hostings_from_st_video_urls (见上文)的那个无法正常工作,它无法正常工作:如果我从交易表开始,然后请求与交易相关的所有步骤,则它不起作用,它给了我空输出。所以下面的请求是空的,这就是为什么验证前 extract_st_embed_hostings_from_st_video_urls 不执行任何操作,测试套件认为没有步骤可以执行操作。
所以我被困在这里:我的问题是在工厂女孩+rspec-重试+我的特定交易模型的关联步骤属性约束的十字路口
如何在我的测试中关联一个交易和多个步骤,同时使用 rspec-retry 并设法使该测试通过,也就是说,通过设法让 self.steps.reject(&:marked_for_destruction?).each “work”即使在测试环境中,也不会认为没有相关的“步骤”?
EDIT
以下评论提供了更多信息
1/ st_appearance_order_nb
st_appearance_order_nb 只是步骤的一个属性/列。 Ut已添加到Active Admin中直接在交易表单内通过 has_many 关系:
f.inputs "Steps" do
f.has_many :steps,
allow_destroy: true,
heading: false,
new_record: true,
# ensure each new step is automagically assigned a +1st_appearance_order_nb
sortable: :st_appearance_order_nb,
sortable_start: 0 do |step|
step.input :st_appearance_order_nb,
input_html: { readonly: true, disabled: true },
label: "Appearance rank"
step.input :st_video_url,
end
end
模型/deal.rb
before_validation :assign_new_st_appearance_order_nb_values_for_steps_in_case_of_steps_removals
before_validation :check_steps_start_on_zero
before_validation :check_steps_have_no_gap_for_st_appearance_order_nb
# in case one or more Steps are removed, avoid a "hole"
# in the st_appearance_order_nb due to those removals
# includes the other requirement to re-start the ranks at 0
def assign_new_st_appearance_order_nb_values_for_steps_in_case_of_steps_removals
if self.steps.any? && self.steps.select { |st| st.marked_for_destruction? }.any? # restrict this taxing operation to cases where there are removals
remaining_steps = self.steps.reject(&:marked_for_destruction?)
remaining_steps.sort_by(&:st_appearance_order_nb).each_with_index do |step, index|
step.update_attributes st_appearance_order_nb: index
end
end
end
def check_steps_start_on_zero
if self.steps.any?
if self.steps.map{|u| u.st_appearance_order_nb}.min != 0
errors[:base] << "Error on Steps. There must be at least one Step with Appearance rank equal to 0 ."
end
end
end
def check_steps_have_no_gap_for_st_appearance_order_nb
if self.steps.any?
if !array_one_steps_increment?( self.steps.map{|u| u.st_appearance_order_nb} )
errors[:base] << "Error on Steps: you can't have gaps inside the list of Appearance ranks. Only increments by one. Change the Appearance ranks accordindly."
end
end
end
def array_one_steps_increment?(array)
sorted = array.sort
lastNum = sorted[0]
sorted[1, sorted.count].each do |n|
if lastNum + 1 != n
return false
end
lastNum = n
end
true
end
EDIT
经过几天的搜索没有成功,有点放弃,但以一种有意义的方式:确实,也许如此多的困难来自于我在功能规范中测试这一点,实际上我不应该让应用程序回调“自行设置”(通过 set 方法)那些有问题的属性(例如 st_embed_hosting),所以我选择在功能测试中自己模拟它们,并进行实际测试以查看回调是否在模型规范中工作。希望它会更加一致和有效。