Search code examples
ruby-on-railsrspec-railsrails-enginesruby-on-rails-7

In a Rails engine Is it possible for Rspec to make use of Rspec support system helpers from another engine?


Given a Rails engine_one that has a spec support file engine_one/spec/support/system/order_functions.rb, containing functionality to support the testing of various order system tests such as simulating a logged in user, adding products to an order etc and contains methods such as log_visitor_in that get used extensively when testing order processing etc...

So now in engine_two that extends some ordering functionality from engine_one I wish to add a new system test that first has to log a visitor in. So how can I make use of that support method from from engine_one?

So far I have mounted the engines in the dummy app I have required engine_one in engine_two/lib/engine.rb I have required the support file in the relevant test but it can't be found and obviously I have added engine_one to engine_two.gemspec

engine_two/spec/rails_helper.rb

require 'engine_one' # and any other gems you need

engine_two/lib/engine_two/engine.rb

require 'engine_one'

in the relevant system test I have the following

engine_two/spec/system/new_payment_methods_spec.rb

require 'rails_helper'
include EngineOne::System

    RSpec.describe "order_payment_feature", type: :system do
      before do
        driven_by(:rack_test)
      end
    
      it "has order payment options" do
        log_visitor_in
      end
    end

This results in the following error

Failure/Error: include EngineOne::System

NameError:
  uninitialized constant EngineOne::System
  Did you mean?  SystemExit

And the helper

module System
  def log_visitor_in()
    administrator = create(:visitor)
    visit ccs_cms.login_url
    fill_in 'login_name', with: visitor.login_name
    fill_in 'Password', with: visitor.password
    click_button 'Login'
  end

end

I have tried with a require instead of an include but that results in a file not found error Plus I have tried changing the include path to

include EngineOne::Spec::Support::System resulting in the same error

So I guess I'm looking for the correct path but I am stuck or missing some other way to include the helper. These are Rails 7 engines.


Solution

  • When you require a file, ruby searches for it relative to paths in $LOAD_PATH; spec/ or test/ are not part of it.

    app directory is a special one in rails, any subdirectory automatically becomes part of autoload_paths. Auto load paths can be seen here ActiveSupport::Dependencies.autoload_paths.

    Any classes/modules defined inside app/* directories can be used without requiring corresponding files. Rails v7 uses zeitwerk to automatically load/reload files by relying on the 'file name' to 'constant name' relationship. That's why folders map to namespaces and files map to classes/modules.

    To fix your issue put any shared code where it can be grabbed with require. Type $LOAD_PATH in the console:

    >> $LOAD_PATH
    => 
    ["/home/alex/code/stackoverflow/lib",
     "/home/alex/code/stackoverflow/vendor",
     "/home/alex/code/stackoverflow/app/channels",
     "/home/alex/code/stackoverflow/app/controllers",
     "/home/alex/code/stackoverflow/app/controllers/concerns",
     "/home/alex/code/stackoverflow/app/helpers",
     "/home/alex/code/stackoverflow/app/jobs",
     "/home/alex/code/stackoverflow/app/mailers",
     "/home/alex/code/stackoverflow/app/models",
     "/home/alex/code/stackoverflow/app/models/concerns",
    
     "/home/alex/code/stackoverflow/engines/question/lib",   # <= engine's lib looks good
    
     "/home/alex/code/stackoverflow/engines/question/app/components",
     "/home/alex/code/stackoverflow/engines/question/app/controllers",
     "/home/alex/code/stackoverflow/engines/question/app/controllers/concerns",
     ...
    

    Put shared files in engines's lib directory. Since we're outside of app directory, rails is not the boss anymore, any path and filename combination will work.

    # question/lib/testing_support/blah.rb                   # <= note the filename
    module System
      def log_visitor_in
        administrator = create(:visitor)
        visit ccs_cms.login_url
        fill_in 'login_name', with: visitor.login_name
        fill_in 'Password', with: visitor.password
        click_button 'Login'
      end
    end
    

    Now that file can be required

    # test/test_helper.rb or spec/rails_helper.rb 
    # after environment and rails requires
    
    require "testing_support/blah"                           # => loads System module
    
    # ...
    

    That's it, use it in your spec

    require 'rails_helper'
    RSpec.describe "order_payment_feature", type: :system do
      include System # include is for modules; now we have its functions in this spec
    
      before { log_visitor_in }
    
      it 'should accept this answer' do
        visit 'questions/71362333'
        expect(page).to have_content('accepted')
      end
    end
    

    Additionally you can require your files any way you wish with an absolute path, regardless of $LOAD_PATH.

    require EngineOne::Engine.root + 'spec/support/system/order_functions.rb'
    
    # or something else
    Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each { |f| require f }