Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

訂正

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

元々の 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 が複数回実行されるとか怖いし、なんか対処法はないものか…。

参考

HAZI
主要言語は JavaScript, Ruby, Perl など。最近は Ruby 中心。 業務システム開発を主に行っている。その他、デザインなども。
http://hazi.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away