How to configure RSpec in Ruby on Rails
ArticleArticle for Junior developers on how to set up: Rails 5.2, RSpec 3.7, Factory Bot, Database Cleaner
Article was written 2018-08-09 and applies for current versions of gems
Configure fresh project
I’m assuming the reader has set up Ruby and Ruby on Rails on his computer after reading book Agile Web Development with Rails or https://guides.rubyonrails.org/getting_started.html
We are going to generate fresh Ruby on Rails project withot the native Ruby on Rails tests.
To display all options for fresh project you can lunch:
rails new -h
To generate fresh new project without Rails tests we will run:
rails new my_rspec_project_name --skip-test
Next open the Gemfile
with your editor and insert into the section group :development, :test
this gems:
# Gemfile
# ...
group :development, :test do
# ...
gem 'database_cleaner'
gem 'factory_bot_rails'
gem 'rspec-rails'
gem 'faker'
end
# ...
It’s critical to have this gems in
group :development, :test do
not justgroup :test do
as default commands likerails generate ...
are running underdevelopment
environment, therefore they will not pick up default config overrides by this gems unless you run:RAILS_ENV=test rails generate ...
Now install the gems:
bundle install
Now generate configuration files for RSpec
rails generate rspec:install
more at: https://github.com/rspec/rspec-rails
Now you should be able to generate Rails models with RSpec tests and FactoryBot factories:
rails generate model worker name:string age:integer
…after you lunch this generator you should see output like this:
invoke active_record
create db/migrate/20180809131148_create_workers.rb
create app/models/worker.rb
invoke rspec
create spec/models/worker_spec.rb
invoke factory_bot
create spec/factories/workers.rb
As you can see this also generated db migration file db/migrate/20180809131148_create_workers.rb
with content:
class CreateWorkers < ActiveRecord::Migration[5.2]
def change
create_table :workers do |t|
t.string :name
t.integer :age
t.timestamps
end
end
end
So lets run the db migrations:
# migrations for develompent environment
rake db:migrate
# migrations for test environment
RAILS_ENV=test rake db:migrate
…this will generate the workers
databaset table
Let write some test
open the spec/models/worker_spec.rb
write your first “failing” test:
require 'rails_helper'
RSpec.describe Worker, type: :model do
it do
expect(1 + 200).to eq(0)
end
end
Now run rspec spec
. You should see output like this:
Failures:
1) Worker should eq 0
Failure/Error: expect(1 + 200).to eq(0)
expected: 0
got: 201
(compared using ==)
# ./spec/models/worker_spec.rb:6:in `block (2 levels) in <main>'
Finished in 0.00714 seconds (files took 0.59673 seconds to load)
3 examples, 1 failure, 1 pending
Great ! Now lets make this test pass. Change:
# ...
expect(1 + 200).to eq(0)
# ...
to:
# ...
expect(1 + 200).to eq(201)
# ...
Now you should see something like:
Finished in 0.00328 seconds (files took 0.61603 seconds to load)
3 examples, 0 failures, 1 pending
Don’t worry about the “pending” test. You can remove those later.
What is important is that you have 0 failures
.
That means you have passing tests.
Working with Factories (FactoryBot)
https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md
Book Agile Web Development with Rails
work with Fixture
tests which valid way how to write tests but for purpouse of this and future tutorials we will use
Factories
.
You can read more about Factories in this article https://robots.thoughtbot.com/why-factories
We will use library FactoryBot
Before we start we need to configure one thing. Open
spec/rails_helper.rb
and add this line inside the RSpec configuration
block:
# ...
RSpec.configure do |config|
# ...
config.include FactoryBot::Syntax::Methods
end
This will ensure we can work with Factory Bot syntax natively in our RSpec tests.
Open the spec/factories/workers.rb
file and change autogenerated content from:
FactoryBot.define do
factory :worker do
name "MyString"
age 1
end
end
to
FactoryBot.define do
factory :worker do
name "Ezo"
age 31
end
end
Now open the spec/models/worker_spec.rb
and add one more test:
require 'rails_helper'
RSpec.describe Worker, type: :model do
# ....
describe 'default worker details' do
let(:worker) { create :worker }
it 'should initialize worker with name and age' do
expect(worker.name).to eq("Ezo")
expect(worker.age).to eq(31)
end
end
describe 'default worker details' do
before do
create :worker
end
it 'should initialize worker with name and age' do
expect(Worker.count).to eq 1
w = Worker.last
expect(w.name).to eq("Ezo")
expect(w.age).to eq(31)
end
end
end
If you run rspec spec
all tests should pass 4 examples, 0 failures, 1 pending
Now these are just some stupid tests that will create a Worker
in our
database with some attributes and we just test if the attributes have
those values.
In reality you will be testing more complex logic with RSpec such as
def trigger_money_transfer(account)
account.balance = account.balance + 800
end
# ...
let(:bank_account) { create :account }
it "should transfer buch of money to my Account" do
expect(bank_account.balance).to eq 0
trigger_money_transfer(bank_account)
expect(bank_account.balance).to eq 800
end
# ...
B.T.W. FactoryBot is a rewrite of older gem with the name FactoryGirl If you stumble upon any FactoryGirl mentions on the internet most of the functionality will work on FactoryBot
Database cleaner
DatabaseCleaner is a gem that will help you keep your database without records before every test run.
All you need to do is in spec/rails_helper.rb
and add this lines inside the RSpec configuration
block:
# ...
RSpec.configure do |config|
# ...
config.before(:suite) do
DatabaseCleaner.strategy = :deletion
end
config.before(:each) do |example|
DatabaseCleaner.clean
end
end
In our context it will delete records from test DB before every test
There is more to this gem, there are different strategies so that the entire test suite is faster. Read more at https://github.com/DatabaseCleaner/database_cleaner
Faker
https://github.com/stympy/faker
Sometimes you want to have random data in your tests (E.g. random email address) as that helps you discover problems you would normally spot only in production when real data starts pouring into your system.
You can generate random data by using FactoryBot sequence syntax:
FactoryBot.define do
factory :worker do
sequence :name do |n|
"Ezo#{n}"
end
age 31
end
end
That will produce:
Ezo1
Ezo2
Ezo3
# ...
But still you will end up with pretty simmilar data in your system. There is a better way how to have truly random data with Faker gem:
Faker::Name.first_name
# => "Jonathan"
Faker::Name.first_name
#=> "Luigi"
Faker::Name.first_name
#=> "Crissy"
Faker::Number.number(2)
#=> "75"
Faker::Number.number(2)
#=> "48"
Faker offers lot of different “fake data” types. You can explore them here. Try to play around with variations to see what feels right.
What is common in Rails world is to combine Faker with Factory bot for generating random data:
in spec/factories/workers.rb
FactoryBot.define do
factory :worker do
name { Faker::Name.first_name }
age 31
end
end
So our test could look like (spec/models/worker_spec.rb
)
require 'rails_helper'
RSpec.describe Worker, type: :model do
# ....
describe 'default worker details' do
let(:worker) { create :worker }
it 'should initialize worker with name and age' do
expect(worker.name).to be_kind_of(String)
expect(worker.name).to worker.name
end
end
end
Now again this is useless test not helping the developer much, I just want to show you that when you are dealing with random data you cannot just compare whether the result equals a string. You need to check if the result equals the state and type of an object.
You don’t want to have Random data all the time, I would even argue that most of the time it’s healthier to work with dereministic data. To understand why pls read this article
Discussion
Entire blog website and all the articles can be forked from this Github Repo