Reputation: 1590
I write test for my model Poll. Check, creating object poll with nested atributes vote_option. Its my 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
I created test and check, that object was created. 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 run poll_spec.rb and givess an error: 1) Poll Failure/Error: @poll = FactoryGirl.create(:poll_with_vote1) NoMethodError: undefined method `vote_option=' for #
Why is this error? What is wrong with my factories?
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
Upvotes: 2
Views: 1915
Reputation: 44675
The reason you're getting this error is because you do not have association with such a name. Try:
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
That being said, couple of personal practices:
Your tests are the documentation of your code. Anyone who is to work with that code in the future should be able to read your tests and to understand what given bit of code is supposed to do. Hence - your tests have to be extremely readable.
Try avoiding creating factories like, poll_with_vote1
- they are not helpful in understanding what is hapenning and you are loosing the power of traits. Just use it like: create :poll, :with_vote_option_1
. It will also allow you to use both traits at the same time with create :poll, :with_vote_option_1, :with_vote_option_2
(ignoring issues with those traits for now, see next point)
Traits are to make your factories more clear. Good example might be a trait published
wrapping logic of status 1
for enum field. Your traits are actually hiding from anyone reading your tests the actual hardcoded value of the vote option, which will produce Why the hell is he expecting 'C#' here?
. There are better ways to do this.
It is good to randomize your factories rather then relying on hardcoded values. It will force you to write clearer tests. For example test expect(page).to have_content('Ruby')
is much less self-explanatory than expect(page).to have_content(vote_option.title)
. You can't do first option with random factories. Also your test will suddenly get useless when you change the page to have title 'Ruby' - which again cannot happen with random data (Well, it can, but it will rather pass randomly with a very small probability of missing the bug)
You should always try to make your factories valid. In your example, factory poll
is not.
This is how I would write factories for your models (using FFaker gem to randomize data)
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
Upvotes: 3