创建对象 FactoryGirl 时未定义的方法。

Undefined method when creating an object FactoryGirl.

我为我的模型投票编写测试。检查,创建具有嵌套属性的对象轮询 vote_option。这是我的 Factories.rb:

FactoryGirl.define do

  factory :vote_option do
    title "Ruby"
    poll
  end

  factory :poll do
    topic "What programming language are you using?"

    trait :vote_option1 do
      association :vote_option, title: "C#"
    end

    trait :vote_option2 do
      association :vote_option, title: "Ruby"
    end

    factory :poll_with_vote1, traits: [:vote_option1]
    factory :poll_with_vote2, traits: [:vote_option2]
  end
end   

我创建了测试并检查,该对象已创建。 poll_srec.rb:

require 'rails_helper'

RSpec.describe Poll, type: :model do
  #let(:user) { FactoryGirl.create(:user) }

  before do
    @poll = FactoryGirl.create(:poll_with_vote1)

  end

  subject{@poll}

  it { should be_valid }

end

I 运行 poll_spec.rb 并给出错误: 1) 投票 Failure/Error: @poll = FactoryGirl.create(:poll_with_vote1) 没有方法错误: #

的未定义方法 `vote_option='

为什么会出现这个错误?我的工厂出了什么问题?

Just in case model Poll:
class Poll < ActiveRecord::Base
  VOTE_OPTIONS_MIN_COUNT = 1

  has_many :vote_options, dependent: :destroy
  has_many :votes

  validates :topic, presence: true
  #validates :vote_options, presence: true #association_count: { minimum: VOTE_OPTIONS_MIN_COUNT }


  #validates :user_id, presence: true

  accepts_nested_attributes_for :vote_options, :reject_if => :all_blank,
                                                  :allow_destroy => true

  def normalized_votes_for(option)
    votes_summary == 0 ? 0 : (option.votes.count.to_f / votes_summary) * 100
  end

  def votes_summary
    vote_options.inject(0) { | summary, option | summary + option.votes.count  }
  end
end

您收到此错误的原因是您与这样的名称没有关联。尝试:

FactoryGirl.define do

  factory :vote_option do
     title "Ruby"
  end

  factory :poll do

    topic "What programming language are you using?"

    trait :vote_option1 do
      after(:create) {|poll| poll.vote_options << create(:vote_option, title: 'C#')         
    end

    trait :vote_option2 do
      after(:create) {|poll| poll.vote_options << create(:vote_option, title: 'Ruby')         
    end

    factory :poll_with_vote1, traits: [:vote_option1]
    factory :poll_with_vote2, traits: [:vote_option2]
  end
end

也就是说,一些个人实践:

  • 您的测试就是代码的文档。将来使用该代码的任何人都应该能够阅读您的测试并理解给定的代码应该做什么。因此 - 你的测试必须非常可读。

  • 尽量避免创建像 poll_with_vote1 这样的工厂 - 它们无助于理解正在发生的事情,并且您正在失去特征的力量。就像这样使用它:create :poll, :with_vote_option_1。它还将允许您通过 create :poll, :with_vote_option_1, :with_vote_option_2 同时使用这两个特征(暂时忽略这些特征的问题,请参阅下一点)

  • Traits是为了让你的工厂更加清晰。很好的例子可能是枚举字段的特征 published 包装 status 1 的逻辑。您的特征实际上对任何阅读您测试的人隐藏了投票选项的实际硬编码值,这将产生 Why the hell is he expecting 'C#' here?。有更好的方法可以做到这一点。

  • 最好随机化您的工厂,而不是依赖硬编码值。它将迫使您编写更清晰的测试。例如,测试 expect(page).to have_content('Ruby')expect(page).to have_content(vote_option.title) 少得多 self-explanatory。你不能对随机工厂做第一选择。此外,当您将页面更改为具有标题 'Ruby' 时,您的测试会突然变得毫无用处——随机数据也不会发生这种情况(好吧,它可以,但它会随机通过,而错过错误的可能性很小)

  • 您应该始终尝试使您的工厂有效。在您的示例中,工厂 poll 不是。

这就是我为您的模型编写工厂的方式(使用 FFaker gem 随机化数据)

FactoryGirl.define do

  factory :vote_option do
     title { FFaker::Lorem.word }
  end

  factory :poll do

    topic { FFaker::Lorem.sentence + '?' }

    transient do    # In old version `ignore do`
      vote_options_number { rand(1..4) }
    end

    after(:build) do |poll, ev|
      poll.vote_options << build_list :vote_option, ev.vote_options_number
    end

end