5

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?

Viktor
  • 2,623
  • 3
  • 19
  • 28
  • https://github.com/mileszs/wicked_pdf (and a few other gems I can't think of right now) generate a Rails app as part of the setup before running tests. You could also make a minimal Rails app and embed it as part of your test suite, but that might not help you track differences between different versions of Rails. Here's how you might be able to minimize the code for that: https://christoph.luppri.ch/articles/rails/single-file-rails-applications-for-fun-and-bug-reporting/ – Unixmonkey May 19 '20 at 15:43
  • @Unixmonkey Thanks a lot for the comment! I went through the wicked pdf repo, but I wasn't able to pinpoint where exactly the rails app is generated or deleted. Looking at the `test/test_helper.rb` file makes me think the app already exists outside of the repo? Am I wrong to assume so? – Viktor May 19 '20 at 16:38
  • The default rake task in `Rakefile` calls `:setup_and_run_tests`, which runs `:dummy_generate`, then `:test`, which is configured to run tests inside the generated app, and `test_helper.rb` (loaded from tests) copies some assets over if needed. – Unixmonkey May 19 '20 at 17:46
  • @Unixmonkey Oh, I missed that, sorry. I think that solves my problem. If you have the time, you can post it as an answer I'll gladly accept it. Thanks a lot! – Viktor May 19 '20 at 22:20

2 Answers2

3

Check out Thoughbot's Appraisal gem:

Appraisal integrates with bundler and rake to test your library against different versions of dependencies in repeatable scenarios called "appraisals."

Here is a guide on how to set it up, including setting up a micro Rails app within your tests dir.

Luke Abel
  • 243
  • 2
  • 9
  • I was completely unaware of this gem and it is awesome – engineersmnky May 19 '20 at 20:18
  • 1
    Hey Luke, I went through the guide, even though I had some issues along the way that weren't covered in the guide (I had to remove `require 'rails/all` because of the missing manifest.js file and change sqlite3 setup a bit) but I eventually got it working. Then I realised that a single file is probably not what I want since, as I've mentioned in the question, I'm looking to test file generation, and without a proper rails structure I won't be able to do that. Probably should have thought about that before starting the guide. Nonetheless thanks a lot for taking the time to answer! – Viktor May 19 '20 at 22:16
3

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.

Unixmonkey
  • 18,485
  • 7
  • 55
  • 78