下記の統合テストを行います。
- ログイン成功/失敗
- ログアウトできる
- ユーザー登録成功/失敗
- フォローできること
- フォローをはずせること
- 投稿一覧が閲覧できる
- 新規投稿できる
- 自分の投稿に編集・削除ボタンが表示される
- 他人の投稿には編集・削除ボタンが表示されない
- 投稿を更新できる
- 投稿を削除できる
- 投稿の詳細画面が閲覧できる
- 投稿に対していいねできる
- 投稿に対していいねを外せる
システムスペックを使うための準備
システムスペックでは、ChromeとChromeDriverをローカルにインストールする必要があります。ChromeDriverは、Chrome操作を自動化するのに必要です。
$brew install chromedriver
また、chromeはバージョン変化が激しいため元々導入されている方も予め最新版にアップデートすることをお勧めします。
$brew upgrade chromedriver
railsでchromedriverを使うためにgemのwebdriversを導入します。また、導入されていなければテスト自動化ソフトウェアであるcapybaraとテストフレームワークであるrspec-railsも入れちゃってください!
Gemfile
group :development, :test do
gem 'rspec-rails'
gem 'factory_bot_rails'
gem 'faker'
end
group :test do
gem 'capybara'
gem 'selenium-webdriver'
end
設定ファイルに以下も追加してください。
spec/rails_helper.rb
require 'rspec/rails'
require 'factory_bot'
require 'capybara/rspec'
RSpec.configure do |config|
#FactoryBot.createなどのFactoryBotの部分を省略することができる
config.include FactoryBot::Syntax::Methods
end
rspecは以下のコマンドで生成されます。
$rails g rspec:install
システムスペックをheadlessに設定する
システムスペックはデフォルトでテスト実行するとブラウザが立ち上がりシュミレートを実行します。今回はテストが通っていることが確認できればいいので、シュミレートをオフにします。まずは、rspecのsupportフォルダ下が読み込まれるように設定を加えます。デフォルトで記述されているのでコメントを外してあげてください。
spec/rails_helper.rb
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }
ヘッドレスに設定するため設定用のファイルを追加します。以下のように記述して下さい。
spec/support/driver_setting.rb
RSpec.configure do |config|
config.before(:each, type: :system) do
# Spec実行時、ブラウザが自動で立ち上がり挙動を確認できる
# driven_by(:selenium_chrome)
# Spec実行時、ブラウザOFF
driven_by(:selenium_chrome_headless)
end
end
これでヘッドレスに設定できているはずです。(以下は別のアプリで実装済みものを試しています。)
ログイン・ログアウトのシステムスペック実装
まずはuserのデータが必要なのでfactorybotを設定します。factorybotのデフォルト値は、各カラムの値が全てランダム値となるように設定すると良いらしい。その上で必要な値のみ明示的にして「このテストで重要な値は何か」がわかりやすくなるから。今回は、Fakerを用いてランダムなデータを生成していきます。
spec/factories/users.rb
FactoryBot.define do
factory :user do
name { Faker::Name.name }
email { Faker::Internet.email }
password { 'password' }
password_confirmation { 'password' }
end
end
ログイン・ログアウトのシステムスペックを実装していきます。sessionのrspecのファイルを生成しましょう!
$rails g rspec:system session
下記はログインページのviewです。
app/views/sessions/new.html.slim
main
.login-register-form
.login-register-form-inner
.card.mb-3
= form_with url: login_path, class: 'card-body session_login', local: true do |f|
.form-group
= f.label :email, ' メールアドレス', class: 'bmd-label-floating'
= f.text_field :email, class: 'form-control'
.form-group
= f.label :password, 'パスワード ', class: 'bmd-label-floating'
= f.password_field :password, class: 'form-control'
= f.submit 'ログイン', class: 'btn btn-raised btn-primary', id: 'login'
.card
.card-body
| アカウントをお持ちでないですか?
= link_to "登録する", new_user_path
ログイン・ログアウトのシステムスペックは以下のように実装しました。
sessions_spec.rb
require 'rails_helper'
RSpec.describe "Sessions", type: :system do
let(:user) { create(:user) }
describe 'ログイン' do
context '認証情報が正しい場合' do
it 'ログインできること' do
visit '/login'
fill_in 'メールアドレス', with: user.email
fill_in 'パスワード', with: 'password'
within '.session_login' do
click_on 'ログイン'
end
expect(current_path).to eq root_path
expect(page).to have_content 'ログインしました'
end
end
context '認証情報が誤りがある場合' do
it 'ログインできないこと' do
visit '/login'
fill_in 'メールアドレス', with: user.email
fill_in 'パスワード', with: 'misspassword'
within '.session_login' do
click_on 'ログイン'
end
expect(current_path).to eq login_path
expect(page).to have_content 'ログインに失敗しました'
end
end
end
describe 'ログアウト' do
it 'ユーザーがログアウト後ログイン画面にリダイレクトされること' do
login(user)
click_on 'ログアウト'
expect(current_path).to eq '/login'
expect(page).to have_content 'ログアウトしました'
end
end
end
テストが通るか下記のコマンドで実行してみると、、、
&bin/rspec spec/system/sessions_spec.rb
見事通りました!
ユーザー登録、フォローのシステムスペック実装
まずは、いちいちログインのプロセスをテストに書くのはめんどくさいのでログイン用のモジュールを作成します。supportフォルダ下に以下のファイルを作成します。
spec/support/login_support.rb
module LoginSupport
def login(user)
visit '/login'
fill_in 'メールアドレス', with: user.email
fill_in 'パスワード', with: 'password'
click_on 'login'
end
end
いかに設定を加えればlogin(user)を使うことができます。
rails_helper.rb
config.include LoginSupport, type: :system
下記はユーザー登録のviewです。
app/views/users/new.html.slim
.login-register-form
.login-register-form-inner
.card.mb-3
= form_with model: @user, class: 'card-body', local: true do |f|
= render 'shared/error_messages', object: @user
.form-group
= f.label :name, class: 'bmd-label-floating'
= f.text_field :name, class: 'form-control'
.form-group
= f.label :email, class: 'bmd-label-floating'
= f.text_field :email, class: 'form-control'
.form-group
= f.label :password, class: 'bmd-label-floating'
= f.password_field :password, class: 'form-control'
.form-group
= f.label :password_confirmation, class: 'bmd-label-floating'
= f.password_field :password_confirmation, class: 'form-control'
= f.submit '登録', class: 'btn btn-raised btn-primary'
.card
.card-body
| アカウントをお持ちですか
= link_to "ログインする", login_path
対してユーザー登録のシステムスペックは以下の通りです。
require 'rails_helper'
RSpec.describe "Users", type: :system do
describe 'ユーザー登録' do
context 'ユーザー情報が正しい場合' do
it 'ユーザー登録が成功すること' do
visit '/users/new'
within '.login-register-form' do
fill_in 'ユーザー名', with: 'Rails太郎'
end
fill_in 'メールアドレス', with: 'rails@example.com'
fill_in 'パスワード', with: '12345678'
fill_in 'パスワード(確認)', with: '12345678'
click_on '登録'
expect(current_path).to eq root_path
expect(page).to have_content('ユーザーの作成に成功しました')
end
end
context 'ユーザー情報が誤りがある場合' do
it 'ユーザー登録が失敗すること' do
visit '/users/new'
within '.login-register-form' do
fill_in 'ユーザー名', with: ''
end
fill_in 'メールアドレス', with: ''
fill_in 'パスワード', with: ''
fill_in 'パスワード(確認)', with: ''
click_on '登録'
expect(page).to have_content('ユーザー名を入力してください')
expect(page).to have_content('メールアドレスを入力してください')
expect(page).to have_content('パスワードは3文字以上で入力してください')
expect(page).to have_content('パスワード(確認)を入力してください')
expect(page).to have_content('ユーザーの作成に失敗しました')
end
end
end
describe 'フォロー' do
let!(:user) { create(:user) }
let!(:other_user) { create(:user) }
before do
login(user)
end
it 'フォローができること' do
visit root_path
expect{
within "#follow-area-#{other_user.id}" do
click_on 'follow'
expect(page).to have_css('#unfollow')
end
}.to change(user.follows, :count).by(1)
end
it 'フォローを外せること' do
user.follow(other_user)
visit root_path
expect{
within "#follow-area-#{other_user.id}" do
click_on 'unfollow'
expect(page).to have_css('#follow')
end
}.to change(user.follows, :count).by(-1)
end
end
end
テストを実行すると以下のように通りました!
投稿のCRUD、いいねのシステムスペック実装
最後に投稿関連のシステムスペックを実装していきます。まずは、postのfactorybotのデータを設定します。
spec/factories/post.rb
FactoryBot.define do
factory :post do
content { Faker::Lorem.word }
#複数画像保存の際に、json形式で保存するため対応した([]に値を入れなければならない)
images { [File.open("#{Rails.root}/spec/fixtures/dummy.jpeg")] }
user
end
そしてテストは以下のように実装しました。viewは今回省略します。
posts_spec.rb
require 'rails_helper'
RSpec.describe "Posts", type: :system do
let!(:user) { create(:user) }
let!(:my_post) { create(:post, user: user) }
let!(:other_post1) { create(:post) }
let!(:other_post2) { create(:post) }
describe 'ポスト一覧' do
context 'ログインしている場合' do
before do
login(user)
user.follow(other_post1.user)
end
it 'フォロワーと自分の投稿だけが閲覧できること' do
visit root_path
expect(page).to have_content other_post1.content
expect(page).to have_content my_post.content
expect(page).not_to have_content other_post2.content
end
end
context 'ログインしていない場合' do
it '全てのポストが表示されること' do
visit posts_path
expect(page).to have_content other_post1.content
expect(page).to have_content my_post.content
expect(page).to have_content other_post2.content
end
end
end
describe 'ポスト投稿' do
it '画像を投稿できること' do
login(user)
visit new_post_path
within '#posts_form' do
attach_file '画像', "#{Rails.root}/spec/fixtures/dummy.jpeg"
fill_in '本文', with: "test"
click_on '登録する'
end
expect(page).to have_content '投稿しました'
expect(page).to have_content 'test'
end
end
describe 'ポスト更新' do
before do
login(user)
end
it '自分の投稿に編集ボタンが表示されること' do
visit root_path
within "#post-#{my_post.id}" do
expect(page).to have_css '.edit-button'
end
end
it '他人の投稿には編集ボタンが表示されないこと' do
user.follow(other_post1.user)
visit root_path
within "#post-#{other_post1.id}" do
expect(page).to_not have_css '.edit-button'
end
end
it '投稿を更新できること' do
visit edit_post_path(my_post)
within '#posts_form' do
attach_file '画像', "#{Rails.root}/spec/fixtures/dummy.jpeg"
fill_in '本文', with: "update"
click_on '登録する'
end
expect(page).to have_content('投稿を更新しました')
expect(page).to have_content("update")
end
end
describe '投稿を削除できること' do
before do
login(user)
end
it '自分の投稿に削除ボタンが表示されること' do
visit root_path
expect(page).to have_css '.delete-button'
end
it '他人の投稿には削除ボタンが表示されないこと' do
user.follow(other_post1.user)
visit root_path
within "#post-#{other_post1.id}" do
expect(page).to_not have_css '.delete-button'
end
end
it '投稿を削除できること' do
visit root_path
within "#post-#{my_post.id}" do
page.accept_confirm { find('.delete-button').click }
end
expect(page).to have_content("投稿を削除しました")
expect(page).not_to have_content(my_post.content)
end
end
describe 'ポスト詳細' do
before do
login(user)
end
it '投稿の詳細画面が閲覧できる' do
visit post_path(my_post)
expect(current_path).to eq "/posts/#{my_post.id}"
end
end
describe 'いいね関連' do
before do
login(user)
user.follow(other_post1.user)
end
it 'いいねができること' do
visit root_path
expect{
within "#like_area-#{other_post1.id}" do
find('.like-button').click
end
expect(page).to have_css '.unlike-button'
}.to change(user.like_posts, :count).by(1)
end
it 'いいねを外せること' do
user.like(other_post1)
visit root_path
expect{
within "#like_area-#{other_post1.id}" do
find('.unlike-button').click
end
expect(page).to have_css '.like-button'
}.to change(user.like_posts, :count).by(-1)
end
end
end
テストを実行すると以下のように投稿関連のシステムスペックも通りました!
これで各項目のテストが全て通りました!Rspec関連は、everyday rspecや伊藤淳一先生のQiitaの記事をリファレンスにすればそれなりには書けると思います。お疲れ様でした🙇