How could I create factories for a cyclic association in FactoryGirl?
Asked Answered
G

3

6

I've been having trouble trying to create factories for some objects and associations that I have defined in my project. I have a cyclic kind of association, where an object is associates with two other objects that afterwards join together.

+--------------+           +-------------+
|              |           |             |
| TestCase     +---------> | TestDataGrid|
|              |           |             |
+------+-------+           +------+------+
       |                          |
       |                          |
       |                          |
       v                          v
+--------------+           +--------------+
|              |           |              |
|              |           |              |
| TestVariable |           | TestDataSet  |
|              |           |              |
+------+-------+           +------+-------+
       |                          |
       |                          |
       |                          |
       |                          |
       |     +---------------+    |
       |     |               |    |
       |     |               |    |
       +---> | TestDataValue |<---+
             |               |
             +---------------+

class TestCase < ActiveRecord::Base
  has_many :test_variables, dependent: :destroy
  has_many :test_data_grids
  #...omitted code...
end

class TestVariable < ActiveRecord::Base
  belongs_to :test_case
  has_many :test_data_values
  #...omitted code...
end

class TestDataValue < ActiveRecord::Base
  belongs_to :test_variable
  belongs_to :test_data_set
  #...omitted code...
end

class TestDataSet < ActiveRecord::Base
  belongs_to :test_data_grid
  has_many :test_data_values
  #...omitted code...
end

class TestDataGrid < ActiveRecord::Base
  belongs_to :test_case
  has_many :test_data_sets
  #...omitted code...
end

Basically the association splits in TestCase and is joined again in TestDataValue, how could I create a Factory that opens and closes the circle with the same objects?

Gingivitis answered 7/9, 2012 at 15:20 Comment(1)
Do you really need that? In most of the cases you can mock and stub all that relations. It's extremely hard to maintain such complex factories.Overtrade
I
0

This isn't tested, but:

FactoryGirl.define do

 factory :test_data_value do
  test_data_set
  test_variable
  # attributes
 end

 factory :test_data_set do
  test_data_grid
  # attributes
 end

 factory :test_variable do
  test_case
  # attributes
 end

 factory :test_case do
  # attributes
 end

 factory :test_data_grid do
  test_case
  # attributes
 end
end

Then in specs:

@test_data_value = FactoryGirl.create(:test_data_value)

@test_variable = @test_data_value.test_variable
@test_data_set = @test_data_value.test_data_set
Idolah answered 7/9, 2012 at 17:4 Comment(2)
the problem with this would be that :test_variable and :test_data_grid would create different instances of a :test_case if I am not mistaken, and I really need this to be the same object.Gingivitis
I had similar FactoryGirl questions about complex test setup. I solved it by calling ThoughtBot and buying an hour of their time. I know this is an unusual; I'm suggesting it here because a) it's worked for me, b) you're offering a large bounty so I'm guessing you want a top-quality answer soon, and c) you're asking a good solid question.Botryoidal
G
0

My suggestion would be to not set it up in the factories but in the tests themselves.

Factory file (thanks to @veritas1)

FactoryGirl.define do

  factory :test_data_value do
    # attributes
  end

  factory :test_data_set do
    # attributes
  end

  factory :test_variable do
    # attributes
  end

  factory :test_case do
    # attributes
  end

  factory :test_data_grid do
    # attributes
  end
end

Test file

@test_case = FactoryGirl.create(:test_case)
@test_data_grid = FactoryGirl.create(:test_case)
@test_variable = FactoryGirl.create(:test_case)
@test_data_set = FactoryGirl.create(:test_case)
@test_data_value = FactoryGirl.create(:test_case)
@test_case.test_data_grids << @test_data_grid
@test_case.test_variables << @test_variable
@test_data_grid.test_data_set << @test_data_set
@test_variable.test_data_values << @test_data_value
@test_data_set.test_data_values << @test_data_value

I know it might suck a little, but it sounds like the way to do it. As a thought though, if you're struggling with your tests it is usually a sign you'll struggle with it down the track and you should be redesigning the APIs. I can't see an alternative way of doing it, but you might be able to with the knowledge of the domain.

Gewgaw answered 11/9, 2012 at 14:39 Comment(1)
Thinking about it, you could just use callbacks. Declare the associations as @Idolah said, but then in the TestCase factory, use a after create callback to setup everything similar to how I described before. github.com/thoughtbot/factory_girl/blob/master/…Gewgaw
Z
0

You can do it manually:

test_case = FactoryGirl.build(:test_case)

test = FactoryGirl.build(:test_data_value,
  :test_variable => FactoryGirl.build(:test_variable,
     :test_case => test_case
  ),
  :test_data_set => FactoryGirl.build(:test_data_set,
    :test_data_grid => FactoryGirl.build(:test_data_grid,
       :test_case => test_case
    )
  )
)

test.test_variable.test_case == test.test_data_set.test_data_grid.test_case
# => true

Or write some helper factory:

FactoryGirl.define do
  factory :test_data_value do
    ignore do
      test_case nil
      test_data_grid nil
    end

    test_variable do
      build :test_variable,
        :test_case => test_case 
                      || test_data_grid.try(:test_case) 
                      || build(:test_case)
    end

    test_data_set do
      build :test_data_set, :test_data_grid => test_data_grid || (
        build :test_data_grid, :test_case => test_case || build(:test_case)
      )
    end
  end
end

Test case as common ancestor:

test = FactoryGirl.create :test_data_value, 
  :test_case => FactoryGirl.build(:test_case)

test.test_variable.test_case == test.test_data_set.test_data_grid.test_case
# => true

Test case as common ancestor and the same test_data_grid for each instance:

test_data_grid = FactoryGirl.build :test_data_grid, 
  :test_case => FactoryGirl.build(:test_case)

test = FactoryGirl.create :test_data_value, 
  :test_data_grid => test_data_grid

test.test_variable.test_case == test.test_data_set.test_data_grid.test_case
# => true
Zamudio answered 16/9, 2012 at 20:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.