はじめに
こんにちは、@jinta_02と申します。
個人開発をするにあたり、モデルスペックを書く機会があったので、記録として記事を書きます。
完全未経験者の記事のため、誤りがございましたらコメント等いただけますと幸いです🙇♂️
前提
Ruby: 3.2.2
Rails: 7.0.6
rspec-rails: 6.0.3
モデルスペックとは
RSpecを使用して、Railsアプリケーション内のデータモデル(ActieRecordモデル)をテストするためのスペックの一種。データモデルの振る舞いやバリデーション、データベースの操作などをテストし、モデルの正確性を確認するためのものです。
具体的には以下のようなテストを行います。
-
バリデーションのテスト
モデルが正しいバリデーションを持っているかを確認します。 -
関連付け(アソシエーション)のテスト
belong_to
,has_many
などの関連付けが適切に動いているかを確認します。 -
カスタムメソッドのテスト
モデルに定義されたカスタムメソッドが正しく動作しているかを確認します。 -
コールバックのテスト
モデルのコールバック(before_action
,before_save
)が適切に動作しているかを確認します。 -
データベースの操作テスト
データベースに正しく保存、更新、削除できるかを確認できます。
今回は、ユーザーモデルに関するモデルスペックを書いていきたいと思います。
事前準備(ダミーデータの作成)
事前準備として、FactoryBotでダミーデータを定義しておきます。
FactoryBot.define do
factory :user do
sequence(:name) { |n| "user_#{n}" }
sequence(:email) { |n| "user_#{n}@example.com" }
password { "password" }
password_confirmation { "password" }
role { "general" }
end
end
ダミーデータを定義しておくことで、以下のように簡単にインスタンスを作成したり、保存できたりします。
# ユーザーのインスタンスを作成。
user = FactoryBot.build(:user)
# ユーザーのインスタンスを作成し、データベースへ保存する。
user = FactoryBot.create(:user)
今回のモデルの確認
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
# ユーザーが作成されたら実行する
after_create :create_initial_list
# ユーザーが複数のリストを持てるよう関連付け(1対多)
has_many :shop_saved_lists, dependent: :destroy
# ユーザーが複数のレビューを書けるよう関連付け(1対多)
has_many :reviews, dependent: :destroy
# 一般ユーザーと管理者を区別するようenumの定義
enum role: { general: 0, admin: 1 }
validates :name, presence: true, length: { maximum: 255 }
# ユーザーが作成されたら、リスト('お気に入り')を作成するメソッド
def create_initial_list
shop_saved_lists.create(name: 'お気に入り')
end
# 自身のオブジェクトかを確認するメソッド
def own?(object)
id == object.user_id
end
end
バリデーションに関するテスト
1. モデルのどの部分?
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
validates :name, presence: true, length: { maximum: 255 }
end
今回は、gem 'devise'を使用してユーザー操作を実装したため、deviseの:validatable
が定義されています。
2. 実際に書いてみる
require 'rails_helper'
RSpec.describe User, type: :model do
describe 'ユーザーのバリデーションに関するテスト' do
it '名前、メールアドレス、パスワードがある場合は有効' do
# 有効なユーザーの作成
valid_user = FactoryBot.build(:user)
# 作成したユーザーが有効かを確認
expect(valid_user).to be_valid
end
it '名前がない場合は無効' do
# 名前が空欄のユーザーを作成
user_without_name = FactoryBot.build(:user, name: '')
# 作成したユーザーが無効かを確認
expect(user_without_name).to be_invalid
end
it '名前が255文字を超える場合は無効' do
# 名前が256文字のユーザーを作成
user_more_than_name = FactoryBot.build(:user, name:
'a' * 256)
# 作成したユーザーが無効かを確認
expect(user_more_than_name).to be_invalid
end
it 'メールアドレスがない場合は無効' do
# メールアドレスが空欄のユーザーを作成
user_without_email = FactoryBot.build(:user, email: '')
# 作成したユーザーが無効かを確認
expect(user_without_email).to be_invalid
end
it '重複したメールアドレスは無効' do
# ユーザーを作成し、データベースへ保存
user = FactoryBot.create(:user)
# 保存したユーザーと同じメールアドレスを持ったユーザーを新たに作成
user_duplicate_email = FactoryBot.build(:user, email: user.email)
# 新たに作成したユーザーが無効かを確認
expect(user_duplicate_email).to be_invalid
end
it 'パスワードがない場合は無効' do
# パスワードが空欄のユーザーを作成
user_without_password = FactoryBot.build(:user, password: '')
# 作成したユーザーが無効かを確認
expect(user_without_password).to be_invalid
end
it 'パスワード(確認用)とパスワードが一致しない場合は無効' do
# パスワードが一致しないユーザーを作成
user_password_mismatch = FactoryBot.build(:user, password_confirmation: 'mismatch')
# 作成したユーザーが無効かを確認
expect(user_password_mismatch).to be_invalid
end
end
end
関連付け(アソシエーション)に関するテスト
1. モデルのどの部分?
class User < ApplicationRecord
# ユーザーが複数のリストを持てるよう関連付け(1対多)
has_many :shop_saved_lists, dependent: :destroy
# ユーザーが複数のレビューを書けるよう関連付け(1対多)
has_many :reviews, dependent: :destroy
end
2. 実際に書いてみる
require 'rails_helper'
RSpec.describe User, type: :model do
describe 'ユーザーのバリデーションに関するテスト' do
...
end
describe 'ユーザーの関連付けに関するテスト' do
# 各テストで使用するので事前に作成し、データベースへ保存
before do
@user = FactoryBot.create(:user)
end
it 'shop_saved_listsとの関連付け正しく設定されていること' do
# ユーザーと関連付けされたリストを作成
shop_saved_list = FactoryBot.create(:shop_saved_list, user: @user)
# 「@user.shop_saved_lists」を実行したときに、作成したリストを含んでいるかを確認
expect(@user.shop_saved_lists).to include shop_saved_list
end
it 'reviewsとの関連付けが正しく設定されていること' do
#ユーザーと関連付けされたレビューを作成
review = FactoryBot.create(:review, user: @user)
# 「@user.reviews」を実行した時に、作成したレビューを含んでいるかを確認
expect(@user.reviews).to include review
end
end
end
enum定義に関するテスト
1. モデルのどの部分?
class User < ApplicationRecord
# 一般ユーザーと管理者を区別するようenumの定義
enum role: { general: 0, admin: 1 }
end
2. 実際に書いてみる
require 'rails_helper'
RSpec.describe User, type: :model do
describe 'ユーザーのバリデーションに関するテスト' do
...
end
describe 'ユーザーの関連付けに関するテスト' do
....
end
describe 'ユーザーのenum値に関するテスト' do
it '一般ユーザーが正しいenum値を持つこと' do
# ユーザーを作成し、データベースへ保存
user = FactoryBot.create(:user)
# 作成したユーザーがroleカラムで、'general'を持っているかを確認
expect(user.role).to eq 'general'
end
end
end
インスタンスメソッドに関するテスト
1. モデルのどの部分?
class User < ApplicationRecord
# ユーザーが作成されたら、リスト('お気に入り')を作成するメソッド
def create_initial_list
shop_saved_lists.create(name: 'お気に入り')
end
# 自身のオブジェクトかを確認するメソッド
def own?(object)
id == object.user_id
end
end
2. 実際に書いてみる
# spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
describe 'ユーザーのバリデーションに関するテスト' do
...
end
describe 'ユーザーの関連付けに関するテスト' do
...
end
describe 'ユーザーのenum値に関するテスト' do
..
end
describe 'create_initial_listメソッドに関するテスト' do
it 'ユーザーが作成された時に初期リストが作成されること' do
# ユーザーを作成し、データベースへ保存
user = FactoryBot.create(:user)
# 作成したユーザーがすでに、「'お気に入り'」のリストを保有しているかを確認
expect(user.shop_saved_lists.where(name: 'お気に入り')).to exist
end
end
describe 'own?メソッドに関するテスト' do
# 各テストで使用するので、事前に作成し、データベースへ保存
before do
@user = FactoryBot.create(:user)
end
it 'ユーザーがオブジェクトの所有者である場合は、trueを返すこと' do
# ユーザーと関連付けされたレビューを作成
review = FactoryBot.create(:review, user: @user)
# ユーザーが作成したレビューかを確認
expect(@user.own?(review)).to eq(true)
end
it 'ユーザーがオブジェクトの所有者でない場合は、falseを返すこと' do
# もう一人のユーザーを作成し、データベースへ保存
another_user = FactoryBot.create(:user)
# もう一人のユーザーと関連付けされたレビューを作成
review = FactoryBot.create(:review, user: another_user)
# ユーザーが作成したレビューではないことを確認
expect(@user.own?(review)).to eq(false)
end
end
end
テストを実行
最後にテストを実行して、正常にテストが実行されているかを確認します。
テストを実行するコマンド以下の通りです。
$ bundle exec rspec spec/models/user_spec.rb
上記のコマンドは、特定のファイルを指定して、テストを実行します。
modelスペック全てのテストを実行する場合は、以下のように実行します。
$ bundle exec rspec spec/models
user_spec.rb
のテスト実行結果が以下のようになっていれば、正常です。
最後に
今回は、個人開発を終えた後にまとめてモデルスペックを書いたため、かなり根気のいる作業になりました。
今後の開発では、テスト駆動開発(TDD)を導入して、最初にテストを書いてからコードを実装する方が良いなと感じました。
それでは以上になります。
最後までご覧いただき、ありがとうございました🙇♂️