thoughtbot / factory_bot_rails

Factory Bot ♥ Rails

Home Page:https://thoughtbot.com/services/ruby-on-rails

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

FrozenError when adding a factory called :product

ksnyder opened this issue · comments

On Rails 5.2.1, and FactoryGirl 4.8.0 (factory_girl_rails same version), I copied an existing factory called :product_spec and renamed it :product.

After doing that, the specs won't run. I get this error on every spec file:

An error occurred while loading ./spec/models/user_spec.rb.
Failure/Error: require File.expand_path('../../config/environment', __FILE__)

FrozenError:
  can't modify frozen Array

The contents of the factory don't matter -- if I completely empty the factory, I still get the error. If I rename the factory to :product1, the error goes away. Whether or not the class name is passed doesn't make a difference. It appears :product is some kind of reserved word in factory_girl?

Code that fails:

factory :product, class: Product do
#
end

No, product isn't a reserved word. I think you may be running into #247. Can you provide a backtrace, or any additional information? I haven't been able to reproduce this myself, which makes it difficult to debug.

Here's the backtrace:

Sorry for the font, but the included # and ` characters in the stacktrace seem to trigger Markdown...

An error occurred while loading ./spec/models/user_spec.rb.
Failure/Error: require File.expand_path('../../config/environment', __FILE__)

FrozenError:
  can't modify frozen Array
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/actionpack-5.2.1/lib/action_dispatch/middleware/stack.rb:76:in `insert'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/actionpack-5.2.1/lib/action_dispatch/middleware/stack.rb:76:in `insert'
# ./config/initializers/cors.rb:8:in `<top (required)>'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:281:in `load'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:281:in `block in load'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:253:in `load_dependency'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:281:in `load'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/railties-5.2.1/lib/rails/engine.rb:657:in `block in load_config_initializer'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/notifications.rb:170:in `instrument'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/railties-5.2.1/lib/rails/engine.rb:656:in `load_config_initializer'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/railties-5.2.1/lib/rails/engine.rb:614:in `block (2 levels) in <class:Engine>'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/railties-5.2.1/lib/rails/engine.rb:613:in `each'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/railties-5.2.1/lib/rails/engine.rb:613:in `block in <class:Engine>'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/railties-5.2.1/lib/rails/initializable.rb:32:in `instance_exec'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/railties-5.2.1/lib/rails/initializable.rb:32:in `run'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/railties-5.2.1/lib/rails/initializable.rb:61:in `block in run_initializers'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/railties-5.2.1/lib/rails/initializable.rb:50:in `each'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/railties-5.2.1/lib/rails/initializable.rb:50:in `tsort_each_child'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/railties-5.2.1/lib/rails/initializable.rb:60:in `run_initializers'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/railties-5.2.1/lib/rails/application.rb:361:in `initialize!'
# ./config/environment.rb:5:in `<top (required)>'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `require'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `block in require'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:253:in `load_dependency'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `require'
# ./spec/rails_helper.rb:3:in `<top (required)>'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `require'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `block in require'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:253:in `load_dependency'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:287:in `require'
# ./spec/models/user_spec.rb:1:in `<top (required)>'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:281:in `load'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:281:in `block in load'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:253:in `load_dependency'
# /Users/byofuel/.rvm/gems/ruby-2.5.1/gems/activesupport-5.2.1/lib/active_support/dependencies.rb:281:in `load'
No examples found.

Yeah, this seems to be similar to #247. The error comes from calling insert on a frozen array here: https://github.com/rails/rails/blob/5-2-1/actionpack/lib/action_dispatch/middleware/stack.rb#L76. This error isn't pointing to anything obvious in factory_bot or factory_bot_rails. This issue on Rails also seems related: rails/rails#33745

Since there isn't anything obviously pointing to factory_bot, and there is some indication that it might have been fixed (see #247 (comment)) I'm going to close this issue for now. If you are able to share a sample app that reproduces the error, I would be happy to help debug. Thanks!

Actually I think I was able to reproduce this after all!

I wrote this invalid factory:

Factory.define do
  factory :user 
  factory :user
end

When we run the test suite RSpec starts by loading every spec file. It goes something like this:

  • RSpec loads first_spec.rb
  • first_spec.rb requires rails_helper.rb
  • rails_helper.rb requires config/environment.rb
  • config/environment.rb initializes the Rails application, which includes inserting all the middlewares, then freezing the middleware Stack
  • After the app initializes, factory_bot_rails loads the factory definitions
    config.after_initialize do
    FactoryBot.find_definitions
    end
  • Loading definitions fails with a FactoryBot::DuplicateDefinitionError
  • As a result of this failure config/environment.rb and rails_helper.rb don't ever fully load (i.e. they don't appear in $LOADED_FEATURES)
  • RSpec rescues the error and reports it
    https://github.com/rspec/rspec-core/blob/9d1d0d6aeb8dc87ff4e584cdcf9e8b1a234da2ae/lib/rspec/core/configuration.rb#L2035-L2037
  • But RSpec doesn't give up, it continues trying to load the next spec -> second_spec.rb
  • second_spec.rb requires rails_helper.rb
  • rails_helper.rb requires config/environment.rb
  • 'config/environment.rb' tries to initializes the Rails application again, but it can't insert middleware because the stack is already frozen
  • We get a FrozenError, which RSpec rescues and reports
  • RSpec continues on, and we likely see a whole bunch of FrozenErrors

The FactoryBot::DuplicateDefinitionError does get reported correctly, but I can see how this would be confusing, and how one might miss the FactoryBot::DuplicateDefinitionError because it gets pushed out of sight by a bunch of FrozenErrors.

Actually, it seems like rspec/rspec-core#2568 addressed exactly this