The following feature spec tests CRUD operations for the users resource in a Rails 4.2.8 application. It includes tests for filtering, sorting and pagination features of the index view.
The tests work. However, I would greatly appreciate your feedback and suggestions on how to improve the specs (e.g., efficiency, readability, maintainability, DRYness).
In the index view, the data is displayed within a table. In the show view, it is displayed within a definition list (<dl> tag).
# spec/features/user_crud_spec.rb
require "rails_helper"
def find_by_i18n_title(i18n_key)
# Bootstrap renames the "title" attribute to "data-original-title"
find("[data-original-title='#{I18n.t(i18n_key)}']")
end
# Select items from select2 dropdowns. https://select2.github.io/
def select_select2_option(option_text)
first('.select2-container').click
find('li', text: option_text).click
end
# Inspired by https://stackoverflow.com/a/45287911/6307730
def sort_order_regex(*sort_by_attributes)
/#{User.order(sort_by_attributes)
.map { |u| Regexp.quote(u.email) }
.join(".+")}/
end
RSpec.shared_examples "sort link" do |link_text:, sort_by:|
it "sorts by #{sort_by} when the #{link_text} link is clicked" do
# Trigger the lazy creation of 2 more records, total count is now 3
users
visit admin_users_path
# Check the record's order by matching the order of their e-mails (unique).
initial_order = sort_order_regex(:first_name, :last_name)
tested_order = sort_order_regex(sort_by)
within_table "users_table" do
expect(page).to have_text(initial_order)
click_link(link_text, exact: false)
expect(page).to have_text(tested_order)
end
end
end
RSpec::Matchers.define :have_single_record do |slug|
match do |page|
expect(page).to have_selector("tr.user", count: 1)
expect(page).to have_selector("tr#user_#{slug}")
end
failure_message do |page|
"expected to find a single record in the page with slug #{slug}. " \
"Found #{page.all("tr#user_#{slug}").count} records instead."
end
end
describe "User admin CRUD", type: :feature, js: true do
let(:user) { FactoryGirl.create(:admin_user) }
let(:user_with_associations) do
user = FactoryGirl.create(:user)
masters = FactoryGirl.create_pair(:master, users: [user])
# Quote requires an associated user to be valid. The :quote factory creates
# a user unless we pass an existing one as an argument.
FactoryGirl.create(:quote, master: masters.first, user: user)
user
end
let(:valid_attributes) { FactoryGirl.attributes_for(:user) }
before(:each) { login_as(user) }
describe "Index view" do
let(:users) { FactoryGirl.create_pair(:user) << user }
it "displays all existing users" do
# Trigger the lazy creation of one more user with associations (to
# ensure the associations appear in the index page)
users = [user, user_with_associations]
visit admin_users_path
expect(page).to have_selector("tr.user", count: users.count)
users.each do |user|
# <tr> must have user_x (x is the record slug) as DOM ID
within("#user_#{user.slug}") do
# OPTION 1
expected_regex = /#{[
user.email,
user.role,
user.full_name,
CS.countries[user.country.to_sym],
user.sangha,
user.masters.map(&:name).join(' '),
user.quotes_count
].join(".*")}/
expect(page).to have_text expected_regex
# OPTION 2
# requires <td>s to have their corresponding attribute names as a DOM class
# expect(page).to have_selector ".email", text: user.email
# expect(page).to have_selector ".role", text: user.role
# expect(page).to have_selector ".full_name", text: user.full_name
# expect(page).to have_selector ".country",
# text: CS.countries[user.country.to_sym]
# expect(page).to have_selector ".sangha", text: user.sangha
# expect(page).to have_selector ".masters",
# text: user.masters.map(&:name).join(' ')
# expect(page).to have_selector ".quotes_count",
# text: user.quotes_count
end
end
end
it "loads the show view when the 'show' icon is clicked" do
visit admin_users_path
find_by_i18n_title("actions.show").click
expect(page).to have_current_path(admin_user_path(user))
end
it "loads edit view when the 'edit' icon is clicked" do
visit admin_users_path
find_by_i18n_title("actions.edit").click
expect(page).to have_current_path(edit_admin_user_path(user))
end
it "destroys the user when the 'delete' icon is clicked" do
# Create a new user so the logged-in user does not get deleted
user = FactoryGirl.create(:user)
visit admin_users_path
expect do
within("#user_#{user.slug}") do
page.accept_alert I18n.t("are_you_sure") do
find_by_i18n_title("actions.delete").click
end
end
# Redirect back to the index view after record is destroyed.
# Must be inside the #expect block to ensure the DB operation is
# finished before trying to count records.
expect(page).to have_current_path(admin_users_path)
end.to change(User, :count).by(-1)
end
describe "sorting" do
include_examples "sort link", link_text: "E-mail", sort_by: :email
include_examples "sort link", link_text: "Role", sort_by: :role
include_examples "sort link", link_text: "Country", sort_by: :country
include_examples "sort link", link_text: "Sangha", sort_by: :sangha
include_examples "sort link", link_text: "Quotes", sort_by: :quotes_count
end
describe "filtering" do
it "filters by email" do
# Trigger the lazy creation of 2 additional records. E-mails are unique.
users
visit admin_users_path
fill_in "q_email_cont", with: user.email
click_button "email_search_submit"
expect(page).to have_single_record(user.slug)
# Test the filter's "Clear" button
click_on "Clear"
expect(page).to have_selector("tr.user", count: users.count)
end
it "filters by full_name" do
# The logged in user (created by let!(:user)) has a different name
user = FactoryGirl.create(:user,
first_name: "Unique",
last_name: "Name")
visit admin_users_path
expect(page).to have_selector("tr.user", count: 2)
fill_in "q_full_name_cont", with: "Unique Name"
click_button "email_search_submit"
expect(page).to have_single_record(user.slug)
end
it "filters by role" do
# The logged in user (created by let!(:user)) has :admin role
user = FactoryGirl.create(:user, role: :user)
visit admin_users_path
expect(page).to have_selector("tr.user", count: 2)
select "user", from: "q_role_eq"
# It auto-submits
expect(page).to have_single_record(user.slug)
end
it "filters by country" do
# The country of the logged in user is not "TH" (see the factory)
user = FactoryGirl.create(:user, country: "TH")
visit admin_users_path
expect(page).to have_selector("tr.user", count: 2)
select "Thailand", from: "q_country_eq"
# It auto-submits
expect(page).to have_single_record(user.slug)
end
it "filters by master (select box)" do
# The logged in user (created by let!(:user)) has no masters
master = FactoryGirl.create(:master)
user = FactoryGirl.create(:user, masters: [master])
visit admin_users_path
expect(page).to have_selector("tr.user", count: 2)
select master.name, from: "q_masters_name_cont"
# It auto-submits
expect(page).to have_single_record(user.slug)
end
end
describe "pagination" do
before(:all) do
# Temporarily reduce the limit of users per page to avoid having to
# create a large number of records.
@original_max_per_page = Kaminari.config.default_per_page
Kaminari.config.default_per_page = 2
end
after(:all) do
# Restore original pagination settings
Kaminari.config.default_per_page = @original_max_per_page
end
context "with a single record" do
it "does not paginate" do
visit admin_users_path
expect(page).to have_selector("tr.user", count: 1)
expect(page).to have_no_selector("ul.pagination")
end
end
context "with 3 records and maximum records per page set to 2" do
before(:each) do
# Trigger the lazy creation of 2 more records, total count is now 3
users
visit admin_users_path
end
it "displays 2 records in the 1st page " do
expect(page).to have_selector("ul.pagination")
expect(page).to have_selector("tr.user", count: 2)
end
it "displays 1 record in the 2nd page" do
within "ul.pagination" do
click_link "2"
end
expect(page).to have_current_path(admin_users_path + "?page=2")
expect(page).to have_selector("tr.user", count: 1)
end
end
end
end
describe "#new view" do
it "creates a new user" do
visit new_admin_user_path
expect do
# Implemented in spec/support/helpers/capybara_fill_user_fields.rb
fill_user_fields(valid_attributes)
click_button "Create User"
# Keep this inside the expect block to ensure it waits until the new
# record is created before recounting records.
expect(page).to have_current_path(admin_user_path(User.last))
end.to change(User, :count).by(1)
expect(page).to have_text "successfully created."
expect(page).to have_text "Name #{valid_attributes[:first_name]}"
end
end
describe "#show view" do
it "displays all public attributes" do
# Create a record with all associations to ensure they appear on the
# #show view
user = user_with_associations
visit admin_user_path(user)
[
"E-mail #{user.email}",
"Role #{user.role}",
"Full Name #{user.full_name}",
"Sangha #{user.sangha}",
"Country #{CS.countries[user.country]}",
"State #{CS.states(user.country)[user.state]}",
"City #{user.city}",
"Phone Number #{user.phone_number}",
"Facebook URL #{user.facebook_url}",
"Masters #{user.masters.map(&:name).join(' ')}",
"Quotes #{user.quotes_count.to_s}"
].each do |text|
expect(page).to have_text text
end
end
end
describe "#edit view" do
it "allows an admin to update all public attributes of the user" do
# create two masters to be selected in the "Masters" multiselect box.
masters = FactoryGirl.create_pair(:master)
visit edit_admin_user_path(user)
# Implemented in spec/support/helpers/capybara_fill_user_fields.rb
fill_user_fields(valid_attributes)
# Select 2 items from the "Masters" select box
masters.each { |master| select_select2_option(master.name) }
click_button "Update User"
expect(page).to have_current_path(admin_user_path(User.last))
expect(page).to have_text "successfully updated."
expect(page).to have_text "Name #{valid_attributes[:first_name]}"
end
end
end
You must log in to answer this question.
Explore related questions
See similar questions with these tags.