rails engines rspec testing

A Ruby on Rails Engine does not come with Rspec. The setup is simple which you can find below along with instructions for adding Factory Bot.

Generate an engine

Lets generate a new engine. We typically like to have an engines folder so we will generate our new engine there.

rails plugin new engines/my_feature --mountable --skip-test --dummy_path=spec/dummy

Include the --skip-tests option because we plan on using Rspec instead of MiniTest. We also are specifying the dummy path to create a dummy application for our specs to use.

Add and Configure Rspec

Next step is to add Rspec. Navigate to your engine’s gemspec file. In our case it is located at engines/my_feature/my_feature.gemspec. Complete all of the TODO’s.

Add our development dependency for rspec.

engines/my_feature/my_feature.gemspec

spec.add_development_dependency "rspec-rails"

At this point, you should be able to bundle install from your engine.

root/engines/my_feature $ bundle install

Install rspec

root/engines/my_feature $ rails g rspec:install
  create  .rspec
   exist  spec
  create  spec/spec_helper.rb
  create  spec/rails_helper.rb

If you try to run rspec now, you will likely run into a Load Error. This is because the rails_helper is trying to require an environment file from the engines root. Our environment file is located within our dummy app.

Modify engines/my_feature/spec/rails_helper.rb

# require File.expand_path('../../config/environment', __FILE__)
require File.expand_path('../dummy/config/environment', __FILE__)

You can confirm rspec is installed by running it

$ rspec spec/

  Finished in 0.00037 seconds (files took 0.05945 seconds to load)
  0 examples, 0 failures

Now it is time to add some tests. Let’s generate a User model so we can test.

root/engines/my_feature $ rails g model User

  invoke  active_record
  create    db/migrate/20190406181424_create_my_feature_users.rb
  create    app/models/my_feature/user.rb

Notice that a spec file for User was not generated. To get that setup we will configure our generators in our engine.rb.

Destroy the user model

root/engines/my_feature $ rails d model User

  invoke  active_record
  remove    db/migrate/20190406181424_create_my_feature_users.rb
  remove    app/models/my_feature/user.rb

Update /engines/my_feature/lib/my_feature/engine.rb

module MyFeature
  class Engine < ::Rails::Engine
    isolate_namespace MyFeature

    config.generators do |g|
      g.test_framework :rspec
    end
  end
end

Regenerate our User which should include a spec

root/engines/my_feature $ rails g model User

  invoke  active_record
  create    db/migrate/20190406182129_create_my_feature_users.rb
  create    app/models/my_feature/user.rb
  invoke    rspec
  create      spec/models/my_feature/user_spec.rb

Depending on how you manage your migrations, you may also need to set the migration path to be scoped to our dummy application.

engines/my_feature/spec/rails_helper.rb

ENGINE_ROOT = File.join(File.dirname(__FILE__), '../')

begin
  ActiveRecord::Migrator.migrations_paths = File.join(ENGINE_ROOT, 'spec/dummy/db/migrate')
  ActiveRecord::Migration.maintain_test_schema!
  ...

Create and migrate your test database if you have not already already

root/engines/my_feature $ rails db:create db:migrate RAILS_ENV=test

Run rspec

root/engines/my_feature $ rspec spec/

  Finished in 0.0061 seconds (files took 2.6 seconds to load)
  1 example, 0 failures, 1 pending

Hopefully you see one pending test for our User spec.

Add Factory Girl

Last step, we are going to add factory girl. We will need to make a few changes to get this working. First, add another development dependency to our gemspec.

spec.add_development_dependency "factory_bot_rails"
root/engines/my_feature $ bundle install

Revisit our configuration change to our engine.rb. Modify the block to include factory_bot.

  config.generators do |g|
    g.test_framework :rspec
    g.fixture_replacement :factory_bot
    g.factory_bot dir: 'spec/factories'
  end

We need to ensure our factories will be loaded when we run our test suite. Open the rails helper and add the following:

engines/my_feature/spec/rails_helper.rb

require 'factory_bot_rails'

FactoryBot.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')
FactoryBot.find_definitions

Rspec also needs to include factory bot methods so we can do things like create(:user) and build(:user) in our tests. That can be done by including the methods in our Rspec configure block.

engines/my_feature/spec/rails_helper.rb

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

Next time you generate a model, you should also have a new factory.

root/engines/my_feature $ rails g model Car

  invoke  active_record
  create    db/migrate/20190406184550_create_my_feature_cars.rb
  create    app/models/my_feature/car.rb
  invoke    rspec
  create      spec/models/my_feature/car_spec.rb
  invoke      factory_bot
  create        spec/factories/my_feature/cars.rb

root/engines/my_feature $ rails db:drop db:create db:migrate RAILS_ENV=test

Our Car factory is generated in spec/factories/my_feature/cars.rb.

The factory bot generator might not have name-spaced the class so be sure to check.

factory :my_feature_car, class: 'MyFeature::Car'

Open the car spec and add a test.

engines/my_feature/spec/models/car_spec.rb

it 'should create a car' do
  car = create(:my_feature_car)

  expect(MyFeature::Car.count).to eq(1)
end

Run rspec and we should see a passing car spec.

root/engines/my_feature $ rspec spec/

  Finished in 0.01697 seconds (files took 3.78 seconds to load)
  2 examples, 0 failures, 1 pending

Takeaway

I hope this helped you get rspec setup in your rails engine. As you create more engines, you might start to find these steps repetitive. In a future post, we will explore different ways to reduce the need of the boilerplate configuration.


Found this useful? Know how it can be improved? Get in touch and share your thoughts at blog@hocnest.com