【问题标题】:How could I create factories for a cyclic association in FactoryGirl?我如何在 FactoryGirl 中为循环关联创建工厂?
【发布时间】:2012-09-01 12:10:26
【问题描述】:

我在尝试为我在项目中定义的一些对象和关联创建工厂时遇到了麻烦。我有一种循环类型的关联,其中一个对象与其他两个对象相关联,这些对象随后连接在一起。

+--------------+           +-------------+
|              |           |             |
| 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

基本上关联在TestCase中分裂并在TestDataValue中再次加入,我如何创建一个用相同对象打开和关闭圆的工厂?

【问题讨论】:

  • 你真的需要那个吗?在大多数情况下,您可以模拟和存根所有这些关系。维护如此复杂的工厂非常困难。

标签: ruby-on-rails ruby unit-testing associations factory-bot


【解决方案1】:

您可以手动完成:

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

或者写一些辅助工厂:

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 = 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_data_grid:

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

【讨论】:

    【解决方案2】:

    我的建议是不要在工厂中设置它,而是在测试本身中设置它。

    工厂文件(感谢@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_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
    

    我知道这可能有点糟糕,但听起来像是这样做的方式。不过,作为一个想法,如果您在测试中遇到困难,这通常表明您将在跟踪过程中遇到困难,您应该重新设计 API。我看不到另一种方法,但您也许可以使用该领域的知识。

    【讨论】:

    【解决方案3】:

    这未经测试,但是:

    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
    

    然后在规格中:

    @test_data_value = FactoryGirl.create(:test_data_value)
    
    @test_variable = @test_data_value.test_variable
    @test_data_set = @test_data_value.test_data_set
    

    【讨论】:

    • 如果我没记错的话,问题是 :test_variable 和 :test_data_grid 会创建不同的 :test_case 实例,我真的需要它是同一个对象。
    • 我对复杂的测试设置有类似的 FactoryGirl 问题。我通过打电话给 ThoughtBot 并为他们争取一个小时的时间来解决这个问题。我知道这很不寻常;我在这里建议它是因为 a) 它对我有用,b) 你提供了大量的赏金,所以我猜你很快就会想要一个高质量的答案,c) 你问的是一个很好的可靠问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多