Setup Rspec, factory bot and database cleaner for Rails 5.2.6

Let’s find out how to configure a best test suite in Rails by using Rspec framwork and the other libraries that support rspec for writing smooth tests. We will be removing rails native test folder and other configurations.

For that we will be using Rspec, Factory bot for factories, Database cleaner.

Lets start from adding these gems into our Gemfile

group :development, :test do
  # Rspec testing module and needed libs
  gem 'factory_bot_rails', '5.2.0'
  gem 'rspec-rails', '~> 4.0.0'
end

group :test do
  # db cleaner for test suite 
  gem 'database_cleaner-active_record', '~> 2.0.1'
end

Now do

bunde install # this installs all the above gems

If you have already built the Rails app, your app may contain Rails own test suite. Remove the native rails test suite to add Rspec module to your project.

We use Rspec over rails native test module because rspec provides better helpers and machnism than Rails native test.

in application.rb file comment the following line

# require 'rails/test_unit/railtie'

inside the class Application add this line:

# Don't generate system test files.
config.generators.system_tests = nil

Remove the native rails test folder:

rm -r test/

We use factories over fixtures. Remove this line from rails_helper.rb

config.fixture_path = "#{::Rails.root}/spec/fixtures"

and modify this line to:

config.use_transactional_fixtures = false # instead of true

This is for preventing rails to generate the native test files when we run rails generators.

Database Cleaner

Now we configure the database cleaner that is used for managing data in our test cycles.

Open rails_helper.rb file and require that module

require 'rspec/rails'
require 'database_cleaner'  # <= add here

Note: Use only if you run integration tests with capybara or dealing with javascript codes in the test suite.

“Capybara spins up an instance of our Rails app that can’t see our test data transaction so even tho we’ve created a user in our tests, signing in will fail because to the Capybara run instance of our app, there are no users.”

I experienced database credentials issues:

➜ rspec
An error occurred while loading ./spec/models/user_spec.rb.
Failure/Error: ActiveRecord::Migration.maintain_test_schema!

Mysql2::Error::ConnectionError:
  Access denied for user 'username'@'localhost' (using password: NO)

First I thought of using database cleaner, later I realised that this error is because of my credentials.yml.enc file was corrupted somehow. I don’t know how that happend. So try to edit and see your credentials still exists in this file.

EDITOR="code --wait" bin/rails credentials:edit

Now in the Rspec configuration block we do the Database Cleaner configuration.

Add the following file:

spec/support/database_cleaner.rb

Inside, add the following:

# DB cleaner using database cleaner library
RSpec.configure do |config|
  # This says that before the entire test suite runs, clear 
  # the test database out completely
  config.before(:suite) do
    DatabaseCleaner.strategy = :transaction
    DatabaseCleaner.clean_with(:truncation)
  end

  # This sets the default database cleaning strategy to 
  # be transactions
  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  # include this if you uses capybara integration tests
  config.before(:each, :js => true) do
    DatabaseCleaner.strategy = :truncation
  end

  # These lines hook up database_cleaner around the beginning 
  # and end of each test, telling it to execute whatever 
  # cleanup strategy we selected
  config.before(:each) do
    DatabaseCleaner.start
  end

  config.after(:each) do
    DatabaseCleaner.clean
  end
end

and be sure to require this file in rails_helper.rb

require 'rspec/rails'
require 'database_cleaner'
require_relative 'support/database_cleaner'  # <= here

Configure Factories

Note: We use factories over fixtures because factories provide better features that make writing test cases an easy task.

Create a folder to generate the factories:

mkdir spec/factories

Rails generators will automatically generate factory files for models inside this folder.

A generator for model automatically creating the following files:

spec/models/model_spec.rb
spec/factories/model.rb

Now lets load Factory bot configuration to rails test suite.

Add the following file:

spec/support/factory_bot.rb

and be sure to require this file in rails_helper.rb

require 'rspec/rails'
require 'database_cleaner'
require_relative 'support/database_cleaner'
require_relative 'support/factory_bot'  # <= here

You can see the following line commented

# Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }

You can uncomment it to make all the factories available in your test suite. But I don’t recommend that to load all the files. We will be loading each factory whenver it is necessary.

Final rails_helper.rb file. We won’t use capybara for integration tests. So we are not adding database_cleaner configuration.

# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)
# Prevent database truncation if the environment is production
abort('The Rails environment is running in production mode!') if Rails.env.production?
require 'rspec/rails'
require_relative 'support/factory_bot'

# Checks for pending migrations and applies them before tests are run.
# If you are not using ActiveRecord, you can remove these lines.
begin
  ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
  puts e.to_s.strip
  exit 1
end
RSpec.configure do |config|
  # 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 = false

  config.infer_spec_type_from_file_location!

  # Filter lines from Rails gems in backtraces.
  config.filter_rails_from_backtrace!
  # arbitrary gems may also be filtered via:
  # config.filter_gems_from_backtrace("gem name")
end

A spec directory look something like this:

spec/
  controllers/
    user_controller_spec.rb
    product_controller_spec.rb
  factories/
    user.rb
    product.rb
  models/
    user_spec.rb
    product_spec.rb
  mailers/
    mailer_spec.rb
  services/
    service_spec.rb  
  rails_helper.rb
  spec_helper.rb

References:

https://github.com/rspec/rspec-rails
https://relishapp.com/rspec/rspec-rails/docs
https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#configure-your-test-suite
https://github.com/DatabaseCleaner/database_cleaner

Model Specs

Lets generate a model spec. A model spec is used to test smaller parts of the system, such as classes or methods.

# RSpec also provides its own spec file generators
➜ rails generate rspec:model user
      create  spec/models/user_spec.rb
      invoke  factory_bot
      create    spec/factories/users.rb

Now run the rpsec command. That’s it. You can see the output from rspec.

➜ rspec
*

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) Item add some examples to (or delete) /home/.../spec/models/user_spec.rb
     # Not yet implemented
     # ./spec/models/user_spec.rb:4

Finished in 0.00455 seconds (files took 1.06 seconds to load)
1 example, 0 failures, 1 pending

Lets discuss how to write a perfect model spec in the next lesson.

Rubocop loading issue on VScode

If you are facing issue to load rubocop plugin to your VS code try the following steps to fix it.

The error message will be something like:

rubocop on VScode not working.Error “rubocop is not executable”

  1. First you have to ensure that you have installed ruby in your machine. if you are using docker containers for your project, ruby is installed inside the containers and VS Code cannot find it.
  2. Next install rubocop gem in your machine
                 $ gem install rubocop

3. Next take

  VS Code -> Settings -> search for 'rubocop' in Ruby > Rubocop: Execute Path

add the output of the following command:

        $ which rubocop

4. Reload the rubocop plugin from VS Code.

Now VS Code will get to execute rubocop.

A Ruby on Rails Application without models

This blog is a quick walkthrough of creating a Ruby On Rails application without a model.

Find Rails new options from here:
https://gist.github.com/abhilashak/3c0c62fa62b2f7a439c417b68d032575

Find Gemfile options from here:

http://bundler.io/v1.2/gemfile.html

Install Ruby/Rails using rbenv

$ touch .rbenv-gemsets
$ echo project-name > .rbenv-gemsets
$ rbenv gemset active
$ rbenv install 2.5.3
$ gem install bundler
$ rbenv rehash
$ gem install rails -v 5.2.9
$ rbenv rehash
$ rails new my-new-porject --skip-active-record --skip-bundle -v 5.2.9

Add in Gemfile:

ruby “2.5.3”

comment jbuilder, we don’t need it.

# gem 'jbuilder', '~> 2.8’

Move rbenv gems file to new rails app folder

$ mv .rbenv-gemsets my-new-porject

$ touch .ruby-version

$ echo 2.5.3 > .ruby-version

$ gem install bundler

$ bundle

$ ruby -v
ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-darwin18]

Start Rails server:

$ rails s

Gemfile add:

# Bootstrap Theme

gem 'bootstrap', '~> 4.3.0’

# Slim template Engine

gem 'slim', '>=4.0.1’

Do Bundle Install

$ bundle

Rename css to scss because we use bootsrap mixins and variables that work with scss files

$ mv app/assets/stylesheets/application.css app/assets/stylesheets/application.scss

Import Bootstrap styles in app/assets/stylesheets/application.scss:

// Custom bootstrap variables must be set or imported *before* bootstrap.
@import "bootstrap";

Then, remove all the *= require and *= require_tree statements from the Sass file. Instead, use @import to import Sass files.

Bootstrap JavaScript depends on jQuery
Add jquery-rails to Gemfile:

gem 'jquery-rails', '~> 4.3.4’

Bootstrap tooltips and popovers depend on popper.js for positioning.
Add Bootstrap dependencies and Bootstrap to your application.js:

//= require jquery3
//= require popper
//= require bootstrap-sprockets

While bootstrap-sprockets provides individual Bootstrap components for ease of debugging, you may alternatively require the concatenated bootstrap for faster compilation:

//= require jquery3
//= require popper
//= require bootstrap

Sass: Individual components

All Bootstrap opponents will be imported by default.
You can also import components explicitly. To start with a full list of modules copy _bootstrap.scss file into your assets as _bootstrap-custom.scss. Then comment out components you do not want from _bootstrap-custom. In the application Sass file, replace @import ‘bootstrap’ with:

@import 'bootstrap-custom';

Your application.css:

/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
 * files in this directory. Styles in this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *
 */

/*Custom bootstrap variables must be set or imported *before* bootstrap.
  The available variables can be found: 
  https://github.com/twbs/bootstrap-rubygem/blob/master/assets/stylesheets/bootstrap/_variables.scss
*/
@import "bootstrap";

Your application.js File:

// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require rails-ujs
//= require turbolinks
//= require_tree .
//= require jquery3
//= require popper
//= require bootstrap-sprockets

You can check sample bootstrap forms here:

https://bootsnipp.com/snippets/featured/login-amp-signup-forms-in-panel

Remove cable.js from javascripts # we don’t need this for now

Two ways to load files in Rails

There are two ways that files get loaded in Rails:

It is registered in the autoload process, and you reference a constant that corresponds to the file name. For instance, if you have app/controllers/pages_controller.rb and reference PagesController, app/controllers/pages_controller.rb will automatically be loaded. This happens for a preset list of directories in the load path. This is a feature of Rails, and is not part of the normal Ruby load process.
Files are explicitly required. If a file is required, Ruby looks through the entire list of paths in your load paths, and find the first case where the file you required is in the load path. You can see the entire load path by inspecting $LOAD_PATH (an alias for $:).

Since lib is in your load path, you have two options: either name your files with the same names as the constants, so Rails will automatically pick them up when you reference the constant in question, or explicitly require the module.

I also notice that you might be confused about another thing. ApplicationController is not the root object in the system. Observe:

module MyModule
  def im_awesome
    puts "#{self} is so awesome"
  end
end
class ApplicationController < ActionController::Base
  include MyModule
end

class AnotherClass
end

AnotherClass.new.im_awesome
# NoMethodError: undefined method `im_awesome' for #

You will need to include the module into whatever class you want to use it in.

class AnotherClass
  include MyModule
end

AnotherClass.new.im_awesome
# AnotherClass is so awesome

Of course, in order to be able to include the module in the first place, you’ll need to have it available (using either of the techniques above).

Reference: This note is from a blog/site that I looked for some knowledge.

Get information about your Rails Environment

To get information about your Rails Environment Rails, Ruby, Rack versions use the following command,

$ rake about

About your application's environment
Ruby version 1.9.2 (i686-linux)
RubyGems version 1.8.10
Rack version 1.3
Rails version 3.1.3
JavaScript Runtime therubyracer (V8)
Active Record version 3.1.3
Action Pack version 3.1.3
Active Resource version 3.1.3
Action Mailer version 3.1.3
Active Support version 3.1.3
Middleware Rack::Cache, ActionDispatch::Static, Rack::Lock, Rack::Runtime, Rack::MethodOverride, Rails::Rack::Logger, ActionDispatch::ShowExceptions, ActionDispatch::RemoteIp, Rack::Sendfile, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::ConnectionAdapters::ConnectionManagement, ActiveRecord::QueryCache, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, ActionDispatch::ParamsParser, ActionDispatch::Head, Rack::ConditionalGet, Rack::ETag, ActionDispatch::BestStandardsSupport, Warden::Manager, OmniAuth::Builder
Application root /home/abhi/my_app
Environment development
Database adapter mysql2
Database schema version 20120704103548