Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factory girl, dependent factories

UPDATE

I went back to using Fixtures. IMOP, fixtures are FAR better than factories; easier to use, easier to write, easier to understand (no magic). My suggestion: limit your testing library to the very basics (listen to DHH)...use minitest with fixtures.

original post

In my app a district has many schools, a school has many uses, a user has many accounts, an account has one role. In order to create complete factories for testing I need to create a user and school that persists across factories. Im getting a "stack level too deep" error in my recent attempts.

My user_test.rb

 FactoryGirl.define do

   factory :district do
     name "Seattle"
   end

   factory :school do
     association :primarycontact, factory: :user # expecting this to attach the user_id from factory :user as :primary contact_id in the school model
     association :district, factory: :district # expecting this to attach the :district_id from the :district factory as :district_id in the school model
     name "Test School"
   end

   factory :user do, aliases: [:primarycontact]
     email "[email protected]"
     name "Who What"
     username "wwhat"
     password "123456"
     password_confirmation { |u| u.password }
     association :school, factory: :school # expecting this to create :school_id in the users model, using the :school factory
   end

   factory :role do
     name "student"
   end

   factory :account do
     association :user, factory: :user
     association :role, factory: :role
   end

 end

So, I am attempting to do FactoryGirl.create(:account)... which I am expecting to create an account, with the user and role from the factories above, with the user associated with the school that is associated with the district. This is not working for me. Among failing tests I get a "stack level too deep" error. And, I believe my before each DatabaseCleaner.clean is clearing the test db before each new factory.

The test that calls these factories is:

describe "User integration" do

  def log_em_in
    visit login_path
    fill_in('Username', :with => "wwhat")
    fill_in('Password', :with => "123456")
    click_button('Log In')
  end

  it "tests log in" do
    user = FactoryGirl.create(:account)
    log_em_in
    current_path.should == new_user_path
  end

end

.

current_path.should == new_user_path returns unknown method error 'should'

How can I improve this code to nest the factories correctly and get a current_user in order to continue testing?

MODELS

school.rb

  belongs_to :district
  belongs_to :primarycontact, :class_name => "User"
  has_many :users, :dependent => :destroy

user.rb

  belongs_to :school
  has_many :accounts, :dependent => :destroy

district.rb

  has_many :schools

account.rb

  belongs_to :role
  belongs_to :user

role.rb

  has_many :accounts
  has_many :users, :through => :accounts
like image 928
hellion Avatar asked Dec 30 '12 03:12

hellion


1 Answers

Your basic problem is that you have a circular dependency between your user factory and your school factory, caused by the fact that you create a primarycontact (a user) when you create a school, then that user creates a school, and so on.

You can get around this by changing how you define your school association inside the user factory. Before doing that though, I'd suggest as a general rule using the shorthand notation for associations. So replace this:

factory :account do
  association :user, factory: :user
  association :role, factory: :role
end

with this:

factory :account do
  user
  role
end

Using this simplification, the following factories will do what you want without generating any circular dependency:

FactoryGirl.define do

  factory :district do
    name "Seattle"
  end

  factory :school do |school|
    district
    primarycontact
    name "Test School"
    after_build do |s|
      s.primarycontact.school = s
    end
  end

  factory :user do
    email "[email protected]"
    name "Who What"
    username "wwhat"
    password "123456"
    password_confirmation { |u| u.password }
    school
  end

  factory :primarycontact, class: "User" do
    # add any attributes you want the primarycontact user to have here
  end

  factory :role do
    name "student"
  end

  factory :account do
    user
    role
  end

end

Notice that what I have done is to create a factory for primarycontact with the class: "User" option. Unlike the user factory, this factory does not create the school by default, avoiding the circular dependency.

Then in the school factory, I use an after_build callback to assign the school itself to the school association on primarycontact, rather than creating a new school (which was causing the problem in your factories).

Hope that makes sense. Note that the callback syntax has changed in the more recent version of factory_girl, see the documentation for details.

like image 131
Chris Salzberg Avatar answered Oct 11 '22 03:10

Chris Salzberg