ruby-on-railsrubyrubygemscucumberaruba

How to test a gem that depends on Rails and uses Rails commands


I'm making a gem that executes Rails commands (rails g model Item for example). When I use it in a Rails project, everything works. The problem is testing it in development outside of a Rails project.

I'm using cucumber with aruba to test if CLI commands execute the proper rails commands and generate the expected files. Unfortunately, when I try to test the behaviour it fails because there are no rails files and the commands require to be run inside of a Rails project in order to work.

I have added a rails dependency to the gemspec:

Gem::Specification.new do |spec|
  spec.add_development_dependency 'rails', '~> 5.2.4'
end

I've thought about creating a new rails project on test start and then deleting it after the tests run, but that seems highly inconvenient. Is there a better way to do this?


Solution

  • A technique we use for WickedPDF is in the default rake task, before we run the tests, is to delete & generate a full Rails application in a gitignored subdirectory of the gem.

    As a high-level simplified example of this Rakefile, it looks something like this:

    Rakefile

    require 'rake'
    require 'rake/testtask'
    
    # This gets run when you run `bin/rake` or `bundle exec rake` without specifying a task.
    task :default => [:generate_dummy_rails_app, :test]
    
    desc 'generate a rails app inside the test directory to get access to it'
    task :generate_dummy_rails_app do
      if File.exist?('test/dummy/config/environment.rb')
        FileUtils.rm_r Dir.glob('test/dummy/')
      end
      system('rails new test/dummy --database=sqlite3')
      system('touch test/dummy/db/schema.rb')
      FileUtils.cp 'test/fixtures/database.yml', 'test/dummy/config/'
      FileUtils.rm_r Dir.glob('test/dummy/test/*') # clobber existing tests
    end
    
    desc 'run tests in the test directory, which includes the generated rails app'
    Rake::TestTask.new(:test) do |t|
      t.libs << 'lib'
      t.libs << 'test'
      t.pattern = 'test/**/*_test.rb'
      t.verbose = true
    end
    

    Then, in test/test_helper.rb, we require the generated Rails app, which loads Rails itself and it's environment:

    test/test_helper.rb

    ENV['RAILS_ENV'] = 'test'
    
    require File.expand_path('../dummy/config/environment.rb', __FILE__)
    require 'test/unit' # or possibly rspec/minispec
    
    # Tests can go here, or other test files can require this file to have the Rails environment available to them.
    # Some tests may need to copy assets/fixtures/controllers into the dummy app before being run. That can happen here, or in your test setup.
    

    You could skip parts of Rails that aren't needed by customizing the command that generates the app. For example, your gem may not need a database at all or a lot of things by default, so you command could be customized for a simpler app. Something like this maybe:

    system("rails new test/dummy --skip-active-record \
      --skip-active-storage --skip-action-cable --skip-webpack-install \
      --skip-git --skip-sprockets --skip-javascript --skip-turbolinks")
    

    In the WickedPDF project, we wanted to test across a wide range of "default" Rails installs, so we don't customize the command much, but that may generate much more than what you need to test some generator tasks.

    WickedPDF also tests against multiple versions of Rails with TravisCI and multiple Gemfiles, but this could also be accomplished with the Appraisal gem that Luke suggested in this thread.