Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factory Girl Traits

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!

like image 473
Paul Avatar asked Jul 06 '13 00:07

Paul


People also ask

What does Factory Girl do?

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.

What is trait in FactoryBot?

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.

What is trait in Rspec?

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.

What is trait in Ruby?

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.


2 Answers

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.

like image 137
fivedigit Avatar answered Oct 26 '22 23:10

fivedigit


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.

like image 40
Chris Peters Avatar answered Oct 26 '22 21:10

Chris Peters