While trying to write rSpec tests, im a bit confused on how to generate FactoryGirl records with associations.
Basically, I have a Quiz
model and a Question
model. They are related through a HABTM association.
Here is my Quiz
factory:
FactoryGirl.define do
factory :quiz do
description 'Test'
# after(:create) { |quiz| quiz.create_sample_questions }
# trait :with_questions do
# after :create do |quiz|
# 5.times do |q|
# quiz.questions << FactoryGirl.create(:question, :with_answers)
# end
# end
# end
end
end
Is it best to create a trait here, and then create sample questions for a quiz? or should I use the after create method to do this?
Neither seem to work, and my trait doesn't seem to generate questions.
Thanks!
Factory Girl provides a well-developed ruby DSL syntax for defining factories like user, post or any other object—not only Active Record objects. “Plain” Ruby classes are perfectly fine. You start by setting up a define block in your factories. rb file.
FactoryBot's traits are an outstanding way to DRY up your tests and factories by naming groups of attributes, callbacks, and associations in one concise area. Imagine defining factories but without the attributes backed by a specific object.
Rspec has great feature and that is trait. In rspec we create factory for each class which provides the simplest set of attributes necessary to create an instance of that class. Many times we need some attributes which we do not want to add in original factory and at the same time we do not want to repeat it.
Traits are somewhat like Abstract Classes in PHP. They're almost like taping on additional methods onto a class. trait Meows { public function meows() { echo 'meow'; } } This Meows trait will add the meowing capability to any class that uses the trait.
I prefer to use traits as they make the specs less cluttered. Of course it's important to make sure the factories themselves don't become too cluttered.
Any time I define a trait which builds an association, I make sure I can build a variable number of records in the association, which FactoryGirls allows to do quite easily:
FactoryGirl.define do
factory :quiz do
description 'Test'
trait :with_questions do
ignore { question_count 5 }
questions { build_list(:question, question_count) }
end
end
end
You can now build or create quizes in your specs:
FactoryGirl.create(:quiz, :with_questions)
FactoryGirl.build(:quiz, :with_questions, question_count: 2)
Note that the trait uses build_list
, so that it doesn't persist the questions by default, and question_count
rather than questions_count
so that it will never conflict with counter caching.
To make sure your specs aren't doing more than they might have to, I would not build questions with answers, but rather just questions (unless your validations require them). If at some point you do need questions with answers, you could add another trait:
FactoryGirl.define do
factory :quiz do
description 'Test'
ignore { question_count 5 }
trait :with_questions do
questions { build_list(:question, question_count) }
end
trait :with_answered_questions do
questions { build_list(:question, question_count, :with_answers) }
end
end
end
For more information on using associations with FactoryGirl, see the FactoryGirl documentation.
I'd be interested in seeing other answer to this question, but I can relate my personal experience with using factories, associations, and auto-loading a bunch of objects with callbacks.
Basically, I find that the "fancier" I try to get with traits and callbacks, the more trouble I cause for the future of my specs. With this kind of twisted logic, you end up with messes of factories where you scratch your head and wonder what the heck is going on.
Totally hypothetical question: what if later you need a quiz with 4 questions? Well, every quiz
:with_questions
has 5 questions included. Do you then create a trait called with_4_questions
? ;)
Based on this experience of mine, I would advise that you start by generating the separate models within your specs and keep it simple. Don't abstract away too much logic behind callbacks.
So let's say that I'm writing a feature spec using your model. This is how I would do what you're trying to accomplish:
feature 'User edits a question' do
let!(:quiz) { FactoryGirl.create(:quiz) }
before do
5.times { FactoryGirl.create(:question, quiz: quiz) }
end
scenario 'with valid input' do
# ...
end
end
If you find yourself needing a quiz
with 5 question
s often, you could create a macro using the method that Railscasts demonstrates. At least then you can create a method that you can pass parameters into, like num_questions
from my totally hypothetical question above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With