如何让FactoryBot返回正确的STI子类?

问题描述 投票:0回答:5

我正在对我的系统进行重大更改,因此我将一个主表更改为 STI,并创建子类来实现特定行为。

class MainProcess < ApplicationRecord
end

class ProcessA < MainProcess
end

class ProcessB < MainProcess
end

在应用程序代码中,如果我运行

MainProcess.new(type: 'ProcessA')
它将返回我想要的 ProcessA 。 但在 Rspec 测试中,当我运行
FactoryBot::create(:main_process, type: 'ProcessA')
时,它返回 MainProcess 并破坏了我的测试。

我的因素是这样的

FactoryBot.define do
  factory :main_process do
    foo { 'bar' }
  end

  factory :process_a, parent: :main_process, class: 'ProcessA' do
  end

  factory :process_b, parent: :main_process, class: 'ProcessB' do
  end
end

有什么方法可以让FactoryBot具有与正常程序相同的行为吗?

ruby-on-rails rspec factory-bot
5个回答
6
投票

我找到了解决方案

FactoryBot.define do
  factory :main_process do
    initialize_with do
      klass = type.constantize
      klass.new(attributes)  
    end
  end
  ...
end

答案在这里找到http://indigolain.hatenablog.com/entry/defining-factory-for-sti-defined-model(日语)

编辑#1:

⚠⚠⚠重要⚠⚠⚠

此处所述,

initialize_with
是私有FactoryBot API的一部分。

根据文档

此方法是私有 API 的一部分。如果可能的话,您应该避免使用此方法,因为它可能会在将来被删除或更改。

因此,如果可以的话,请避免使用。 (虽然我没有找到任何其他方法来达到这个结果而不使用它)

编辑#2

除了 gem 文档中的警告(如上所述)之外,GETTING_STARTED.md 实际上建议您使用它

如果你想使用factory_bot来构造一个对象,其中一些属性被传递来初始化,或者如果你想要做一些除了简单地在构建类上调用new之外的事情,你可以通过在你的工厂上定义initialize_with来覆盖默认行为


3
投票

initialize_with
被标记为 FactoryBot 私有 API 的一部分,不建议外部使用。

我认为你可以使用嵌套工厂来完成此任务。

  factory :process do

    factory :type_a_process, class: Process::TypeA do
      type {"Process::TypeA"}
    end

    factory :type_b_process, class: Process::TypeB do
      type {"Process::TypeB"}
    end

  end
end

FactoryBot.create(:type_b_process)

2
投票

如果您只是修改原始代码以将类指定为类类型而不是字符串,则它可以工作:

FactoryBot.define do
  factory :main_process do
    foo { 'bar' }
  end

  factory :process_a, parent: :main_process, class: ProcessA do
  end

  factory :process_b, parent: :main_process, class: ProcessB do
  end
end

这是 FactoryBot 文档的相关部分


1
投票

这样更好:

initialize_with { type.present? ? type.constantize.new : 发票.new }

https://dev.to/epigene/simple-trick-to-make-factorybot-work-with-sti-j09


0
投票

如果您想使用

traits
,可以这样做:

FactoryBot.define do
  factory :main_process do
    foo { 'bar' }
    type { 'DefaultProcess' }

    initialize_with { type.constantize.new }

    trait :process_a do
      type { 'ProcessA' }
    end

    trait :process_b do
      type { 'ProcessB' }
    end
  end
end
© www.soinside.com 2019 - 2024. All rights reserved.