LoginSignup
3
2

More than 5 years have passed since last update.

[訂正] FactoryBot(FactoryGirl) の before/after で stack level too deep する問題への対処

Last updated at Posted at 2018-06-30

訂正

オモクソ書き方を間違えてた

元々の stack level too deep が出たコード

FactoryBot.define do
  factory :product do
    name "MyString"
    maker nil
  end

  before(:create) do |product|
    product.maker = create(:maker)
  end
end

正しくは下記のように書きます

FactoryBot.define do
  factory :product do
    name "MyString"
    maker nil

    before(:create) do |product|
      product.maker = create(:maker)
    end
  end
end

以上!

以下元記事

現象

Maker model と、 Product model があるような状態で、spec を書く時こんな感じで FactoryBot を書いててたら stack level too deep が出た。

FactoryBot.define do
  factory :product do
    name "MyString"
    maker nil
  end

  before(:create) do |product|
    product.maker = create(:maker)
  end
end
$ bundle exec rspec -fd spec/models/product_spec.rb

Product
  example at ./spec/models/product_spec.rb:4 (FAILED - 1)
  example at ./spec/models/product_spec.rb:5 (FAILED - 2)

Failures:

  1) Product 
     Failure/Error: product.maker = create(:maker)

     SystemStackError:
       stack level too deep
     # ./spec/factories/products.rb:19:in `block (2 levels) in <main>'
     # ./spec/factories/products.rb:19:in `block (2 levels) in <main>'
     # ./spec/factories/products.rb:19:in `block (2 levels) in <main>'
     # ./spec/factories/products.rb:19:in `block (2 levels) in <main>'
     # ./spec/factories/products.rb:19:in `block (2 levels) in <main>'
     # ./spec/factories/products.rb:19:in `block (2 levels) in <main>'
     # ./spec/factories/products.rb:19:in `block (2 levels) in <main>'
     # ./spec/factories/products.rb:19:in `block (2 levels) in <main>'
     # ./spec/factories/products.rb:19:in `block (2 levels) in <main>'
     # ./spec/factories/products.rb:19:in `block (2 levels) in <main>'
     # ./spec/factories/products.rb:19:in `block (2 levels) in <main>'
     # ./spec/factories/products.rb:19:in `block (2 levels) in <main>'
     # ./spec/factories/products.rb:19:in `block (2 levels) in <main>'
...

原因

理由は単純で before(:create) 内で、 create(:maker) を実行すると、before(:create) が再度実行されて永久ループに入る。

:product のイベントなのに、create(:maker) でも実行されちゃう。

訂正: factory :product block 内に書くべきなのが block 外に書いている為、 FactoryBot.define block で実行されてしまていた為、全ての create イベントで実行されてしまっていた。

対処法

対処法は下記の通り。

FactoryBot.define do
  factory :product do
    name "MyString"
    maker { build(:maker) }
  end
end

何も書かないのが吉?

ちなみに、下記のように何も書かずに maker だけ宣言すると勝手にcreate(:maker)してくれる。

# spec/factories/products.rb
FactoryBot.define do
  factory :product do
    name "MyString"
    maker
  end
end
# spec/models/product_spec.rb
RSpec.describe Product, type: :model do
  it { expect(create(:product)).to be_a(described_class) } #pass
  it { expect(create(:product).maker).to be_a(Maker) } #pass
end
$ bundle exec rspec -fd spec/models/product_spec.rb

Product
  should be a kind of Product(id: integer, name: string, maker_id: integer, created_at: datetime, updated_at: datetime)
  should be a kind of Maker(id: integer, name: string, created_at: datetime, updated_at: datetime)

Finished in 0.03272 seconds (files took 1.64 seconds to load)
2 examples, 0 failures

Factories 定義で create しない方がいい?

ただ、この時 Makercreate しちゃうので、最初の例のように before(:create) を使って何かしようとすると、before(:create) は2回実行される。

# spec/factories/products.rb
FactoryBot.define do
  factory :product do
    name "MyString"
    maker
  end

  before(:create) do |product|
    puts "Create!!"
  end
end
# spec/models/product_spec.rb
RSpec.describe Product, type: :model do
  it { expect(create(:product)).to be_a(described_class) }
  it { expect(create(:product).maker).to be_a(Maker) }
end
$ bundle exec rspec spec/models/product_spec.rb
Create!!
Create!!
.Create!!
Create!!
.

Finished in 0.03598 seconds (files took 1.74 seconds to load)
2 examples, 0 failures

んー、この仕様は如何なんだろう。
今までこの仕様を把握しないでよく使ってこれたなとびっくりしている。

引数なしの自動 create の影響で、callback が複数回実行されるとか怖いし、なんか対処法はないものか…。

参考

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2