sRails 4.2 / Rspec / rspec-retry – 关联属于/ has_many失败

我正面临着一个关于rspec问题的怪物,每次我试图解决它创造另一个错误。

我有一个模特交易,其中有很多步骤

模型/ step.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 

我所有错误的要点是如何进行我的functionrspec测试工作,我将关联交易和步骤 。 我曾经使用工厂女孩“通常”的瞬态,这将更加清洁,但我不得不离开它(见下文),因为我们有特殊要求:

 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 

我之所以不使用这种“瞬态”技术来创建与交易相关的多个步骤,则我们的应用程序非常具体。

当你拿到一个给定的交易所有相关的步骤的st_appearance_order_nb(整数)时,它必须始终是: – 从0开始 – 然后没有间隙所以1,2,3 ……

交易模型交易的一些before_validations允许我确保始终如此。 您不能处理其中一个步骤的appearance_nb为1而另一个步骤的appearance_nb为3但没有步骤具有appearance_nb 2的关联步骤。并且您不能没有具有appearance_nb的步骤它必须是一系列0,1,2,3 ……等等

这实际上仍然可以使用“经典瞬态”方式在Factory Girl中创建步骤。 但是上面创建Steps的经典“瞬态”方式的问题是我有一个名为“rspec-retry”的gem ,可以帮助我重新进行function测试,就像许多其他具有复杂UI / javascript页面的rspec用户一样我的前端测试第一次失败了一些js /加载原因然后如果你重复它们,第二次或第三次它将工作。 因此,我的大多数function测试都运行了4次,有些只通过了第2次或第3次:)gemrspec重试很整洁但是有一个非常重要的问题,“瞬态”方式我正在创建与Factory Girl相关的步骤:

我遇到了错误,因为如果测试失败了第一次“重试”,第二次,就像测试应用程序认为st_appearance_order_nb nb 0到4已经被采用(实际上它们是由第一个rspec“try”创建的),所以它现在创建4个新步骤,分别为st_appearance_order_nb,分别为5,6,7和8。

然后……导致错误,因为我有一个before_validation来确保Deal的关联Steps的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%的测试都有效,但现在发布的post:我的一个测试失败了:因为非常奇怪的是我将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测试时,我只看到“beacon1”,而不是beacon2(在dev和prod中看到这两个消息)

我想知道为什么它没有被执行。

所以我在测试中添加了一些self.steps.reject(&:marked_for_destruction?).each do |step|为什么行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.steps 1 .to_json”给出“null”,这与之前的put一致

    • 但netx 2看跌期权带来更多困惑:

    “puts deal_with_videos.id”给出3

    “puts deal_with_videos_step0.deal_id”也给了我3

所以进入2个方向,我有相同的信息,这很奇怪:看起来它们实际上很好地联系在一起。 很奇怪,因为它与前两个投注相矛盾。

  • 但最后的“推杆”给了我:

puts deal_with_videos_step0.deal.title给sme“title deal1”

puts deal_with_videos_step0.to_json给了我一个内容详细的json(这里没有复制以保持简洁)=>他们都工作

我的结论

这就像我所说的那样只有一种方式 :如果我开始执行像deal_with_videos_step0这样的步骤,那么使用.deal来达到Deal表,它就可以了。

但在另一方面,我的before_validation中的一个名为extract_st_embed_hostings_from_st_video_urls(见上文),它不能正常工作,它不起作用: 如果我从表开始交易然后请求所有与交易相关的步骤,它不起作用 ,它给了我空的输出。 所以下面的请求是空的,这就是为什么在validation之前extract_st_embed_hostings_from_st_video_urls没有做任何事情,测试套件认为没有做任何事情的步骤。

所以我在这里被封锁了:我的问题是在工厂女孩的交叉路口+ rspec-retry +我的特定交易模型的相关步骤属性约束

我如何在我的测试中将一个Deal和多个Steps关联起来,同时使用rspec-retry并设法让这个测试通过,也就是说通过设置self.steps.reject(&:marked_for_destruction?)。每个“工作”甚至在测试环境中,而不是认为没有关联的“步骤”?

编辑

下面的注释提供了更多信息

1 / st_appearance_order_nb

  • 创建

st_appearance_order_nb只是Step的一个属性/列。 通过has_many关系直接在Deal表单中的Active Admin中添加:

 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 

编辑

经过几天的搜索没有成功,有点放弃,但有一种方式是有意义的:事实上可能有很多困难来自我在function规范中测试这个事实,其中实际上我不应该让应用程序回调“设置自己”(通过set方法)那些有问题的属性(例如st_embed_hosting)所以我选择在Feature测试中自己模拟它们并进行实际测试以查看回调是否在模型规范中有效。 希望它会更加一致和有效。