Beginners introduction to testing Ruby On Rails application with RSpec and Capybara.
-
Install rails without
minitest
:rails new rails-rspec-tutorial -T
; -
Add
rspec
toGemfile
:
group :development, :test do
gem 'rspec-rails', '~> 3.7'
end
-
Install dependencies:
bundle install
; -
Execute
bundle exec rails generate rspec:install
to create a/spec
dir; -
Run rspec:
bundle exec rspec
- Create first spec:
require "rails_helper"
RSpec.describe "hello spec" do
describe "math" do
expect(6 * 7).to eq(43)
end
end
- And catch an error:
Failures:
1) hello spec math should be able to perform basic math
Failure/Error: expect(6 * 7).to eq(43)
expected: 43
got: 42
- Fix the error to get test passed:
require "rails_helper"
RSpec.describe "hello spec" do
describe "math" do
it "should be able to perform basic math" do
# expect(6 * 7).to eq(43) # => false
expect(6 * 7).to eq(42)
end
end
end
- Add another spec with empty string:
require "rails_helper"
RSpec.describe "hello spec" do
# ...
describe String do
let(:string) { String.new }
it "should provide an empty string" do
expect(string).to eq("")
end
end
end
- Create model
Article
:
bundle exec rails g model Article title:string body:text active:boolean
RAILS_ENV=test bundle exec rake db:migrate
- Create
spec/models/article_spec.rb
:
require "rails_helper"
RSpec.describe Article, type: :model do
context "validations tests" do
it "ensures the title is present" do
article = Article.new(body: "Content of the body")
expect(article.valid?).to eq(false)
end
it "ensures the body is present" do
article = Article.new(title: "Title")
expect(article.valid?).to eq(false)
end
it "ensures the article is active by default" do
article = Article.new(body: "Content of the body", title: "Title")
expect(article.active?).to eq(true)
end
it "should be able to save article" do
article = Article.new(body: "Content of the body", title: "Title")
expect(article.save).to eq(true)
end
end
context "scopes tests" do
end
end
- Add some presence validators to
app/models/article.rb
:
class Article < ApplicationRecord
validates_presence_of :title, :body
end
- Create a migration:
class MakeArticleActiveByDefault < ActiveRecord::Migration[5.1]
def change
change_column :articles, :active, :boolean, default: true
end
end
- Run migration:
bundle exec rake db:migrate
- Add scope specs:
require "rails_helper"
RSpec.describe Article, type: :model do
# ...
context "scopes tests" do
let(:params) { { body: "Content of the body", title: "Title", active: true } }
before(:each) do
Article.create(params)
Article.create(params)
Article.create(params)
Article.create(params.merge(active: false))
Article.create(params.merge(active: false))
end
it "should return all active articles" do
expect(Article.active.count).to eq(3)
end
it "should return all inactive articles" do
expect(Article.inactive.count).to eq(2)
end
end
end
- Create Articles controller scaffold:
bundle exec rails g scaffold_controller Articles
rm -rf spec/views spec/routing spec/request spec/helpers spec/requests
- Create Articles spec:
require "rails_helper"
RSpec.describe ArticlesController, type: :controller do
context "GET #index" do
it "returns a success response" do
get :index
# expect(response.success).to eq(true)
expect(response).to be_success
end
end
context "GET #show" do
let!(:article) { Article.create(title: "Test title", body: "Test body") }
it "returns a success response" do
get :show, params: { id: article }
expect(response).to be_success
end
end
end
- Add articles to
routes.rb
file:
Rails.application.routes.draw do
resources :articles
end
- Add
--format documentation
to.rspec
- Add Capybara to
Gemfile
:
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
gem 'rspec-rails', '~> 3.7'
gem 'capybara'
end
- Add these lines to your
spec/rails_helper.rb
file:
require 'capybara/rails'
require 'capybara/rspec'
- Create first feature test by running
bundle exec rails g rspec:feature home_page
:
require "rails_helper"
RSpec.feature "Visiting the homepage", type: :feature do
scenario "The visitor should see a welcome message" do
visit root_path
expect(page).to have_text("Welcome to my blog!")
end
end
- Add
root
toroutes.rb
:
Rails.application.routes.draw do
root "home#index"
resources :articles
end
- Generate controller:
bundle exec rails g controller Home index
rm -rf spec/controllers/home_controller_spec.rb spec/views/home spec/views/home/index.html.erb_spec.rb spec/helpers/home_helper_spec.rb
- Modify the view
app/views/home/index.html.erb
:
<h1>Welcome to my blog!</h1>
<p>Find me in app/views/home/index.html.erb</p>
-
bundle exec rails g rspec:feature articles
-
Create feature spec:
require 'rails_helper'
RSpec.feature "Articles", type: :feature do
context "Create new article" do
scenario "Should be successful" do
visit new_article_path
within("form") do
fill_in "Title", with: "Test title"
fill_in "Body", with: "Test body"
check "Active"
end
click_button "Create Article"
expect(page).to have_content("Article successfully created")
end
scenario "Should fail" do
end
end
context "Update article" do
end
context "Remove existing article" do
end
end
- Fix the view (
app/views/articles/_form.erb
):
<%= form_with(model: article, local: true) do |form| %>
<% if article.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:</h2>
<ul>
<% article.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div>
<%= form.label :title %>
<%= form.text_field :title, id: "article_title" %>
</div>
<div>
<%= form.label :body %>
<%= form.text_area :body, id: "article_body" %>
</div>
<div>
<%= form.label :active %>
<%= form.check_box :active, id: "article_active" %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
- Allow attributes in
app/controllers/articles_controller.rb
:
def article_params
params.require(:article).permit(:active, :id, :title, :body)
end
- Full Articles test:
require 'rails_helper'
RSpec.feature "Articles", type: :feature do
context "Create new article" do
before(:each) do
visit new_article_path
within("form") do
fill_in "Title", with: "Test title"
check "Active"
end
end
scenario "should be successful" do
fill_in "Body", with: "Test body"
click_button "Create Article"
expect(page).to have_content("Article was successfully created")
end
scenario "should fail" do
click_button "Create Article"
expect(page).to have_content("Body can't be blank")
end
end
context "Update article" do
let(:article) { Article.create(title: "Test title", body: "Test content") }
before(:each) do
visit edit_article_path(article)
end
scenario "should be successful" do
within("form") do
fill_in "Body", with: "New body content"
end
click_button "Update Article"
expect(page).to have_content("Article was successfully updated")
end
scenario "should fail" do
within("form") do
fill_in "Body", with: ""
end
click_button "Update Article"
expect(page).to have_content("Body can't be blank")
end
end
context "Remove existing article" do
let!(:article) { Article.create(title: "Test title", body: "Test content") }
scenario "remove article" do
visit articles_path
click_link "Destroy"
expect(page).to have_content("Article was successfully destroyed")
expect(Article.count).to eq(0)
end
end
end
- Add to
Gemfile
:
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
gem 'rspec-rails', '~> 3.7'
gem 'capybara'
gem 'selenium-webdriver'
end
- Add to
spec/rspec_helper.rb
:
Capybara.default_driver = :selenium_chrome_headless
- Fix articles spec:
context "Remove existing article" do
let!(:article) { Article.create(title: "Test title", body: "Test content") }
scenario "remove article" do
visit articles_path
expect(Article.count).to eq(1)
accept_alert do
click_link "Destroy"
end
expect(page).to have_content("Article was successfully destroyed")
expect(Article.count).to eq(0)
end
end
-
Rails coverage report:
bundle exec rails stats
-
To install codecov add to your
Gemfile
:
gem 'simplecov', require: false, group: :test
- Add to your
spec/rails_helper.rb
:
require 'simplecov'
SimpleCov.start
- Don't forget to add
coverage/
dir to your.gitignore
file;