Search code examples
ruby-on-railsrubyrspecfactory-botrspec-rails

Loading helper files into FactoryBot files gives `cannot load such file`


I am trying to use a helper method in my FactoryBot file, but when I call require rails_helper I get a require: cannot load such file -- support/geocoder_helper (LoadError).

spec/factories/members.rb

# frozen_string_literal: true

require 'rails_helper'

require 'support/geocoder_helper'

FactoryBot.define do
  factory :member do
    association :user, roles: ['Member']
    cohort

    after(:build) do |member, _evaluator|
      if member.user
        add_geocoder_stub(member.user.full_address_string)
      end
    end
  end
end

spec/support/geocoder_helper.rb

# frozen_string_literal: true

DEFAULT_GEOCODER_STUB_RESULTS = [
  {
    'coordinates' => [11, -13],
    'address' => '123 Main St, Los Angeles, CA, USA',
    'state' => 'Los Angeles',
    'state_code' => 'CA',
    'country' => 'United States',
    'country_code' => 'US'
  }.freeze
].freeze

def add_geocoder_stub(address, results = DEFAULT_GEOCODER_STUB_RESULTS)
  address = User.new(address).full_address_string if address.is_a?(Hash)

  Geocoder::Lookup::Test.add_stub(address, results)
end

spec/support/factory_bot.rb

# frozen_string_literal: true

RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end

spec/rails_helper.rb

# frozen_string_literal: true

# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'

ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)

...

Result:

in `require': cannot load such file -- support/geocoder_helper (LoadError)

This result is the same for all different helpers and all factories. I am able to use helpers in non FactoryBot files using the same require patterns.

Versions: rails (5.2.4.6)

rspec (3.9.0)

factory_bot (4.10.0)


Solution

  • spec folder is not in your load_path. You can either:

    • use absolute path require Rails.root.join('support/geocoder_helper')

    • use relative require require_relative '../support/geocoder_helper'

    • add spec folder to your load (in spec_helper.rb): $LOAD_PATH << Rails.root.join('spec')

    • require all your support files in spec_helper, which is pretty standard procedure

    Other issues: do not define methods on main object. It is quite a terrible practice that I keep fixing in most of the specs, causing a lot of really odd behaviours and subtle bugs. Once the geocoder_helper file is loaded, add_geocoder_stub method is defined on Object class making it available in every single object globally (that is including nil, true, all symbols etc). This is just, well, yucky - not to mention it might cause unexpected behaviour changes as suddenly respond_to? will return true and you just don't know what other libraries might be using this.

    Instead, wrap your method in modules:

    module GeocoderHelper
      module_function
    
      DEFAULT_GEOCODER_STUB_RESULTS = [
        ...
      ].freeze
    
      def add_geocoder_stub(address, results = DEFAULT_GEOCODER_STUB_RESULTS)
        address = User.new(address).full_address_string if address.is_a?(Hash)
    
        Geocoder::Lookup::Test.add_stub(address, results)
      end
    end
    

    and use it only when required:

    after(:build) do |member, _evaluator|
      if member.user
        GeocoderHelper.add_geocoder_stub(member.user.full_address_string)
      end
    end
    
    RSpec.configure do |rspec|
      rspec.include GecoderHelper
    end
    

    EDIT

    As it seems you are able to require support files from your spec files then it means that you already do option 3 from the above, but FactoryBot is required before this happens (most likely on bundler setup). One option to fix it would be to modify your Gemfile from:

    gem 'factory_bot_rails'
    

    to

    gem 'factory_bot_rails', require: false
    

    which will prevent factories to be loaded on Bundler setup. You then need to load them manually with require 'factory_bot_rails' after you added spec to your load path - most likely in your spec_helper (but also check your rails_helper, config/environments/test.rb and initializers).