こんなことが分かる
- TutorialでのMinitestをRSpecで変換する方法
- Controller/Model spec, Request spec, System specなどの違い
前回:#10 リメンバーミー機能編
次回:#11 プロフィール編集編
注意点
- __Tutorial9章まで__または__本記事#10まで__のテストを__RSpec__で見直します
- Minitest → RSpecに移行する都合上、__異なるファイル名を使用__する場合があります
- リファクタリングの都合上、__別テストに移行、再定義、example名の変更__などを行なっています
リンク先:
Ruby on Rails Tutorial
Rails Tutorialの知識から【ポートフォリオ】を作って勉強する話 #1 準備編
当記事の背景
何specで書けばいいのかを区別するためです。
テストに関するガイドライン
- Contoller/View specは極力不使用
- Request specは結合テストで使用
- System specはブラウザ上のテストに使用
Request spec → コントローラやヘルパーなどがどう行動したのか
System spec → ブラウザ画面がどうなっているのか
この基準で進めます。
Tutorial9章/記事#10までのRSpec公開
以下の順で公開します。
準備系
- rails_helper.rb
- factories
- support
テスト系
- models
- helpers
- requests
- systems
rails_helper.rb
意義:RSpecの設定
spec/rails_helper.rb
# 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', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'rspec/rails'
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
begin
ActiveRecord::Migration.maintain_test_schema!
rescue ActiveRecord::PendingMigrationError => e
puts e.to_s.strip
exit 1
end
RSpec.configure do |config|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = true
config.infer_spec_type_from_file_location!
config.filter_rails_from_backtrace!
config.include FactoryBot::Syntax::Methods
config.include ApplicationHelpers
config.before(:each) do |example|
if example.metadata[:type] == :system
driven_by :selenium, using: :headless_chrome, screen_size: [1400, 1400]
end
end
end
factories
意義:テスト用のActive Record
spec/factories/users.rb
FactoryBot.define do
factory :user do
name { "Michael Example" }
email { "michael@example.com" }
password { "password" }
password_confirmation { "password" }
end
end
support
意義:テスト用のヘルパー
spec/support/application_helper.rb
module ApplicationHelpers
def is_logged_in?
!session[:user_id].nil?
end
end
models
意義:モデルのテスト
spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
let(:user) { create(:user) }
describe "User" do
it "should be valid" do
expect(user).to be_valid
end
end
describe "name" do
it "gives presence" do
user.name = " "
expect(user).to be_invalid
end
context "50 characters" do
it "is not too long" do
user.name = "a" * 50
expect(user).to be_valid
end
end
context "51 characters" do
it "is too long" do
user.name = "a" * 51
expect(user).to be_invalid
end
end
end
describe "email" do
it "gives presence" do
user.email = " "
expect(user).to be_invalid
end
context "254 characters" do
it "is not too long" do
user.email = "a" * 243 + "@example.com"
expect(user).to be_valid
end
end
context "255 characters" do
it "is too long" do
user.email = "a" * 244 + "@example.com"
expect(user).to be_invalid
end
end
it "should accept valid addresses" do
user.email = "user@example.com"
expect(user).to be_valid
user.email = "USER@foo.COM"
expect(user).to be_valid
user.email = "A_US-ER@foo.bar.org"
expect(user).to be_valid
user.email = "first.last@foo.jp"
expect(user).to be_valid
user.email = "alice+bob@baz.cn"
expect(user).to be_valid
end
it "should reject invalid addresses" do
user.email = "user@example,com"
expect(user).to be_invalid
user.email = "user_at_foo.org"
expect(user).to be_invalid
user.email = "user.name@example."
expect(user).to be_invalid
user.email = "foo@bar_baz.com"
expect(user).to be_invalid
user.email = "foo@bar+baz.com"
expect(user).to be_invalid
user.email = "foo@bar..com"
expect(user).to be_invalid
end
it "should be unique" do
duplicate_user = user.dup
duplicate_user.email = user.email.upcase
user.save!
expect(duplicate_user).to be_invalid
end
it "should be saved as lower-case" do
user.email = "Foo@ExAMPle.CoM"
user.save!
expect(user.reload.email).to eq 'foo@example.com'
end
end
describe "password and password_confirmation" do
it "should be present (nonblank)" do
user.password = user.password_confirmation = " " * 6
expect(user).to be_invalid
end
context "5 characters" do
it "is too short" do
user.password = user.password_confirmation = "a" * 5
expect(user).to be_invalid
end
end
context "6 characters" do
it "is not too short" do
user.password = user.password_confirmation = "a" * 6
expect(user).to be_valid
end
end
end
describe "User model methods" do
describe "authenticated?" do
it "return false for a user with nil digest" do
expect(user.authenticated?('')).to be_falsey
end
end
end
end
helpers
意義:ヘルパーのテスト
spec/helpers/application_helper_spec.rb
require 'rails_helper'
RSpec.describe ApplicationHelper, type: :helper do
describe "#full_title" do
context "page_title is empty" do
it "removes symbol" do
expect(helper.full_title).to eq('Lantern Lantern')
end
end
context "page_title is not empty" do
it "returns title and application name where contains symbol" do
expect(helper.full_title('hoge')).to eq('hoge | Lantern Lantern')
end
end
end
end
spec/helpers/sessions_helper_spec.rb
require 'rails_helper'
RSpec.describe SessionsHelper, type: :helper do
let(:user) { create(:user) }
describe "#current_user" do
it "returns right user when session is nil" do
remember(user)
expect(current_user).to eq user
expect(is_logged_in?).to be_truthy
end
it "returns nil when remember digest is wrong" do
remember(user)
user.update_attribute(:remember_digest, User.digest(User.new_token))
expect(current_user).to be_nil
end
end
end
requests
意義:結合テスト
spec/requests/users_logins_spec.rb
require 'rails_helper'
RSpec.describe "UsersLogins", type: :request do
include SessionsHelper
let(:user) { create(:user) }
def post_invalid_information
post login_path, params: {
session: {
email: "",
password: ""
}
}
end
def post_valid_information(remember_me = 0)
post login_path, params: {
session: {
email: user.email,
password: user.password,
remember_me: remember_me
}
}
end
describe "GET /login" do
context "invalid form information" do
it "fails having a danger flash message" do
get login_path
post_invalid_information
expect(flash[:danger]).to be_truthy
expect(is_logged_in?).to be_falsey
end
end
context "valid form information" do
it "succeeds having no danger flash message" do
get login_path
post_valid_information
expect(flash[:danger]).to be_falsey
expect(is_logged_in?).to be_truthy
follow_redirect!
expect(request.fullpath).to eq '/users/1'
end
it "succeeds logout" do
get login_path
post_valid_information
expect(is_logged_in?).to be_truthy
follow_redirect!
expect(request.fullpath).to eq '/users/1'
delete logout_path
expect(is_logged_in?).to be_falsey
follow_redirect!
expect(request.fullpath).to eq '/'
end
it "does not log out twice" do
get login_path
post_valid_information
expect(is_logged_in?).to be_truthy
follow_redirect!
expect(request.fullpath).to eq '/users/1'
delete logout_path
expect(is_logged_in?).to be_falsey
follow_redirect!
expect(request.fullpath).to eq '/'
delete logout_path
follow_redirect!
expect(request.fullpath).to eq '/'
end
it "succeeds remember_token because of check remember_me" do
get login_path
post_valid_information(1)
expect(is_logged_in?).to be_truthy
expect(cookies[:remember_token]).not_to be_empty
end
it "has no remember_token because of check remember_me" do
get login_path
post_valid_information(0)
expect(is_logged_in?).to be_truthy
expect(cookies[:remember_token]).to be_nil
end
it "has no remember_token when users logged out and logged in" do
get login_path
post_valid_information(1)
expect(is_logged_in?).to be_truthy
expect(cookies[:remember_token]).not_to be_empty
delete logout_path
expect(is_logged_in?).to be_falsey
expect(cookies[:remember_token]).to be_empty
end
end
end
end
spec/requests/users_signups_spec.rb
require 'rails_helper'
RSpec.describe "UsersSignups", type: :request do
include SessionsHelper
def post_invalid_information
post signup_path, params: {
user: {
name: "",
email: "user@invalid",
password: "foo",
password_confirmation: "bar"
}
}
end
def post_valid_information
post signup_path, params: {
user: {
name: "Example User",
email: "user@example.com",
password: "password",
password_confirmation: "password"
}
}
end
describe "GET /signup" do
it "is invalid signup information" do
get signup_path
expect { post_invalid_information }.not_to change(User, :count)
expect(is_logged_in?).to be_falsey
end
it "is valid signup information" do
get signup_path
expect { post_valid_information }.to change(User, :count).by(1)
expect(is_logged_in?).to be_truthy
follow_redirect!
expect(request.fullpath).to eq '/users/1'
end
end
end
systems
意義:ブラウザテスト
spec/systems/login_spec.rb
require 'rails_helper'
RSpec.describe "Logins", type: :system do
let(:user) { create(:user) }
def submit_with_invalid_information
fill_in 'メールアドレス', with: ''
fill_in 'パスワード', with: ''
find(".form-submit").click
end
def submit_with_valid_information(remember_me = 0)
fill_in 'メールアドレス', with: user.email
fill_in 'パスワード', with: 'password'
check 'session_remember_me' if remember_me == 1
find(".form-submit").click
end
describe "Login" do
context "invalid" do
it "has no information and has flash danger message" do
visit login_path
expect(page).to have_selector '.login-container'
submit_with_invalid_information
expect(current_path).to eq login_path
expect(page).to have_selector '.login-container'
expect(page).to have_selector '.alert-danger'
end
it "deletes flash messages when users input invalid information then other links" do
visit login_path
submit_with_invalid_information
expect(current_path).to eq login_path
visit root_path
expect(page).not_to have_selector '.alert-danger'
end
end
context "valid" do
it "has valid information and will link to user path" do
visit login_path
submit_with_valid_information
expect(current_path).to eq user_path(1)
expect(page).to have_selector '.show-container'
end
it "contains logout button without login button at user path" do
visit login_path
submit_with_valid_information
expect(current_path).to eq user_path(1)
expect(page).to have_selector '.btn-logout-extend'
expect(page).not_to have_selector '.btn-login-extend'
end
end
end
describe "Logout" do
it "contains login button without logout button at root path" do
visit login_path
submit_with_valid_information
expect(current_path).to eq user_path(1)
expect(page).to have_selector '.btn-logout-extend'
expect(page).not_to have_selector '.btn-login-extend'
click_on 'ログアウト'
expect(current_path).to eq root_path
expect(page).to have_selector '.home-container'
expect(page).to have_selector '.btn-login-extend'
expect(page).not_to have_selector '.btn-logout-extend'
end
end
end
spec/systems/signup_spec.rb
require 'rails_helper'
RSpec.describe "Signups", type: :system do
def submit_with_invalid_information
fill_in '名前', with: ''
fill_in 'メールアドレス', with: 'user@invalid'
fill_in 'パスワード', with: 'foo'
fill_in 'パスワード(再入力)', with: 'bar'
find(".form-submit").click
end
def submit_with_valid_information
fill_in '名前', with: 'Example User'
fill_in 'メールアドレス', with: 'user@example.com'
fill_in 'パスワード', with: 'password'
fill_in 'パスワード(再入力)', with: 'password'
find(".form-submit").click
end
it "is invalid because it has no name" do
visit signup_path
submit_with_invalid_information
expect(current_path).to eq signup_path
expect(page).to have_selector '#error_explanation'
end
it "is valid because it fulfils form information" do
visit signup_path
expect { submit_with_valid_information }.to change(User, :count).by(1)
expect(current_path).to eq user_path(1)
expect(page).not_to have_selector '#error_explanation'
end
end
spec/systems/site_layout_spec.rb
require 'rails_helper'
RSpec.describe "SiteLayouts", type: :system do
describe "home layout" do
it "contains root link" do
visit root_path
expect(page).to have_link nil, href: root_path
end
it "contains signup link" do
visit root_path
expect(page).to have_link 'はじめる', href: signup_path
end
it "contains login link" do
visit root_path
expect(page).to have_link 'ログイン', href: login_path
end
it "returns title with 'Lantern Lantern'" do
visit root_path
expect(page).to have_title 'Lantern Lantern'
end
end
describe "about layout" do
it "returns title with 'About | Lantern Lantern'" do
visit about_path
expect(page).to have_title 'About | Lantern Lantern'
end
end
end
前回:#10 リメンバーミー機能編
次回:#11 プロフィール編集編