Setting up a rails 5 engine with Rspec, Cucumber, Capybara, Shoulda and Factory_girl

Initial Steps

Configure RVM

$rvm --create --rvmrc use ruby_version@project_name

Install Rails

$gem install rails -v 5.0.4

Create the engine

$ rails plugin new <<GemName>> --mountable -T --dummy-path=spec/dummy
  • -T tells the generator to skip Test::Unit
  • –mountable tells the generator that you want a namespaced engine
  • –full tells the generator that you want app and config directories
  • –dummy-path is the rails app that is generated for your tests. It’s called test/dummy by default, but I never liked calling it that.

Git repository

Initiate an empty repository in local machine, at the project directory:

$git init

create the readme file

$touch README.md

add database.yml and secrets.yml to your .gitginore

#.gitignore
spec/dummy/config/database.yml
spec/dummy/config/secrets.yml
.rvmrc
.idea/

Add files to rep

$git add .

Commit all changes to local repo

$git commit -m 'first commit'

Push changes to GitHub

Create the repository at github.com
To create a remote named origin pointing at github repo

$git remote add origin https://github.com/marcelofossrj/b109.git

Sends the commits in the master branch at github

$git push origin master

Configure the Gem to handle migrations

The default behavior of the engine is to copy all migrations found in the engine/db/migrate folder in the target app. It is a better approach that the migrations have their date and name updated in the target application so it do not generate conflicts in the target application migrations. Edit the engine.rb file:

#lib/<<GemName>>/engine.rb
module GemName
   class Engine < Rails::Engine
     isolate_namespace GemName
     initializer :append_migrations do |app|
       unless app.root.to_s.match root.to_s+File::SEPARATOR
         app.config.paths["db/migrate"].concat config.paths["db/migrate"].expanded
       end
     end
   end
end

Now rake db:migrate will find and correctly run the engine’s migrations.

Configure the gemspec file

Open the «GemName».gemspec file and replace all the TODO text with the proper information related to the gem specification.

Configure the test frameworks

Add the reference to Rspec, Capybara and Factory_girl to the gemspec file

#<<GemName>>.gemspec
s.add_dependency "rspec-rails", "~> 3.6.0"  
s.add_dependency "capybara", "~> 2.14.4"  
s.add_dependency "factory_girl_rails", "~> 4.8.0"  
s.add_dependency "shoulda", "~> 3.5.0"  
s.add_dependency "database_cleaner", "~> 1.6.1"  
s.add_dependency "cucumber-rails", "~> 1.5.0"  

Edit the Gemfile to add the reference to the gems

#Gemfile
group :development, :test do
  gem "rspec-rails", "~> 3.6.0"
  gem "factory_girl_rails", "~> 4.8.0"
  gem "shoulda", "~> 3.5.0"
end
group :test do
  gem "database_cleaner", "~> 1.6.1"
  gem "capybara", "~> 2.14.4"
  gem "cucumber-rails", "~> 1.5.0", require: false
  gem "simplecov", "~> 0.14.1"
end

Install and configure RSpec

$bundle install
$rails generate rspec:install

Modify spec/spec_helper.rb to work with the engine file structure:

# spec/spec_helper.rb
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../dummy/config/environment", __FILE__)
require 'rspec/rails'
require 'rspec/autorun'
require 'factory_girl_rails'
require 'capybara/rails'
require 'capybara/rspec'
require 'shoulda/matchers'
require 'database_cleaner'

Dir[<<GemName>>::Engine.root.join("spec/support/**/*.rb")].sort.each {|f| require f}
ENGINE_RAILS_ROOT=File.join(File.dirname(__FILE__), '../')
Dir[File.join(ENGINE_RAILS_ROOT, "spec/support/**/*.rb")].sort.each {|f| require f }

# Checks for pending migrations before tests are run.  
# If you are not using ActiveRecord, you can remove this line.
ActiveRecord::Migration.check_pending! if defined?(ActiveRecord::Migration)

RSpec.configure do |config|
  config.expect_with :rspec do |expectations|
    expectations.include_chain_clauses_in_custom_matcher_descriptions = true
  end
  config.mock_with :rspec do |mocks|
    mocks.verify_partial_doubles = true
  end

  config.disable_monkey_patching!
  config.warnings = false
  config.profile_examples = nil
  config.order = :random # Run specs in random order to surface order dependencies. If you find
  Kernel.srand config.seed

  #Add the following lines to configure database_cleaner
  config.before(:suite) do
     DatabaseCleaner.strategy = :transaction
     DatabaseCleaner.clean_with(:truncation)
  end

  config.around(:each) do |example|
     DatabaseCleaner.cleaning do
       example.run
     end
  end

  config.shared_context_metadata_behavior = :apply_to_host_groups

  # Add this line to configure factory_girl
  config.include FactoryGirl::Syntax::Methods # Factory_girl config

  # Remove this line if you're not using ActiveRecord or ActiveRecord, fixtures
  config.fixture_path = "#{::Rails.root}/spec/fixtures"

  # If you're not using ActiveRecord, or you'd prefer not to run each of your examples within a    
  # transaction, remove the following line or assign false instead of true.
  config.use_transactional_fixtures = true

  # If true, the base class of anonymous controllers will be inferred automatically.
  # This will be the default behavior in future versions of rspec-rails.
  config.infer_base_class_for_anonymous_controllers = false
End

Create the directory where the factories will be created

$mkdir spec/factories

Create the directories to create the rspec tests

$mkdir spec/features
$mkdir spec/support
$mkdir spec/api
$mkdir spec/controllers
$mkdir spec/integration
$mkdir spec/models
$mkdir spec/request

Tell the engine that Rspec is the default testing framework:

# lib/appointly/engine.rb
module GemName
   class Engine < ::Rails::Engine
      isolate_namespace GemName
      initializer :append_migrations do |app|
         unless app.root.to_s.match root.to_s+File::SEPARATOR
            app.config.paths["db/migrate"].concat config.paths["db/migrate"].expanded
         end
      end
      config.generators do |g|
         g.test_framework :rspec, :fixture => false
         g.fixture_replacement :factory_girl, :dir => 'spec/factories'
      end
   end
End

Edit the spec/rails_helper.rb to correct the path for the dummy application and to have RSpec recognizing the rails engine url_helpers

# spec/rails_helper.rb
# Replace the line
# require File.expand_path("../../config/environment", __FILE__)
# to
require File.expand_path("../dummy/config/environment", __FILE__) ##


# Add the following line right after
# RSpec.configure do |config|
config.include <<GemName>>::Engine.routes.url_helpers

Test Rspec installation

Create a scaffold, and run migrations:

$ rails g scaffold tenant name:string subdomain:string
$ rm -rf test # For some reason Rails still generates the test directory
$ rake db:create
$ rake db:migrate RAILS_ENV=test

Runs Spec tests

$bundle exec rake app:db:migrate
$bundle exec rake app:db:test:prepare

or

$ rspec spec/controllers/gem_name/tenants_controller_spec.rb

It will fail…
What I suppose is that since it is an isolated engine (i.e. an engine designed not to interfere with the routes, helpers, etc. defined in your application).
It is necessary to explicitly say that the engine routes are being used, not the dummy app’s routes.
Add the command use_route: :gem_name to your spec, firt to the INDEX action:

#spec/controllers/gem_name/tenants_controller_spec.rb
describe "GET index" do
  it "assigns all tenants as @tenants" do
    tenant = Tenant.create! valid_attributes
    # get :index, {}, valid_session
    get :index, { use_route: :gem_name }, valid_session
    assigns(:tenants).should eq([tenant])
  end
end

and the same to the NEW action

#spec/controllers/gem_name/tenants_controller_spec.rb
describe "GET #new" do
  it "returns a success response" do
    #get :new, params: {}, session: valid_session
    get :new, { use_route: :gem_name }, valid_session
    expect(response).to be_success
  end
end

Run the spec again…

$rspec spec/controllers/gem_name/tenants_controller_spec.rb

…and it will pass.
Run rspec for tests or spec/dummy/bin/rails s for running test app

Install and configure Cucumber

$ rails generate cucumber:install

The command will create a features folder under the engine root folder.
Open the features/support/env.rb file and do the following:

# features/support/env.rb
# Add the 3 lines below befor the require cucumber/rails command
ENV["RAILS_ENV"] ||= 'test' ##
require File.expand_path("../../../spec/dummy/config/environment", __FILE__) ##
ENV["RAILS_ROOT"] ||= File.dirname(__FILE__) + '../../../spec/dummy' ##
require 'cucumber/rails'
# Add the following line to use factory_girl
require 'factory_girl_rails'
require File.expand_path(File.dirname(__FILE__) + '/../../spec/factories')