Factory Girl Traits
Asked Answered
B

2

5

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!

Bradshaw answered 6/7, 2013 at 0:28 Comment(1)
Late response but, Can you post your question factory along with how you are calling a quiz with with_questions trait?Boldfaced
T
3

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 questions 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.

Thrashing answered 13/11, 2013 at 20:28 Comment(1)
I posted a different approach, which also answers your totally hypothetical question in a different way.Aves
A
7

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.

Aves answered 14/5, 2014 at 11:32 Comment(1)
It looks like ignore is now transient in factorygirl github.com/thoughtbot/factory_girl/pull/651Motherwort
T
3

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 questions 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.

Thrashing answered 13/11, 2013 at 20:28 Comment(1)
I posted a different approach, which also answers your totally hypothetical question in a different way.Aves

© 2022 - 2024 — McMap. All rights reserved.