訂正
オモクソ書き方を間違えてた
元々の 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 しない方がいい?
ただ、この時 Maker
を create
しちゃうので、最初の例のように 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 が複数回実行されるとか怖いし、なんか対処法はないものか…。