<Rspec> バリデーションテストについてのメモ
前提
Gemfile
group :development, :test do ... gem 'rspec-rails', '~> 4.0.2' gem 'factory_bot_rails' end
schema.rb
create_table "tasks", force: :cascade do |t| t.string "title" t.text "content" t.integer "status" t.datetime "deadline" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "user_id" t.index ["user_id"], name: "index_tasks_on_user_id" end create_table "users", force: :cascade do |t| t.string "email", null: false t.string "crypted_password" t.string "salt" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["email"], name: "index_users_on_email", unique: true end
app/models/user.rb
class User < ApplicationRecord authenticates_with_sorcery! has_many :tasks, dependent: :destroy validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] } validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] } validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] } validates :email, uniqueness: true, presence: true def my_object?(object) object.user_id == id end end
app/models/task.rb
class Task < ApplicationRecord belongs_to :user validates :title, presence: true, uniqueness: true validates :status, presence: true enum status: { todo: 0, doing: 1, done: 2 } end
FactoryBot
Factorybotとは、サンプルデータ(ダミーのインスタンス)を簡単に作成することができるテストツール。
公式では以下のように定義されている。
factory_bot is a fixtures replacement with a straightforward definition syntax, support for multiple build strategies (saved instances, unsaved instances, attribute hashes, and stubbed objects), and support for multiple factories for the same class (user, admin_user, and so on), including factory inheritance.
=> factory_botは、わかりやすい定義構文を持ったフィクチャの代替で、複数のビルドストラテジ(インスタンスの保存や非保存、属性のハッシュ、スタブオブジェクト)をサポートし、継承しているファクトリを含む、同クラス(user、admin_userなど)の複数のファクトリをサポートする。みたいな感じ。
サンプルデータを言い換えたもの。
事前に定義したデータをテスト実行直前testDBに導入できる。
YAMLで記述され、特定のDBに依存しない。
1つのモデルにつき、1つのフィクスチャファイルが作成される。
spec/factories/users.rb
FactoryBot.define do factory :user do sequence(:email) { |n| "person#{n}@example.com" } password { "password" } password_confirmation { "password" } end end
sequenceは一意の属性について定義すると、末尾の文字列または数値がa,b,c...1,2,3...のように順番に振られるようになる。
userのemailの場合最後の文字列ではないので繰り上がっていく場所を指定している。
ちなみに上記のように記載しないと以下のようになる。
# spec/factories/users.rb FactoryBot.define do factory :user do sequence(:email, "person1@example.com") password {"password"} password_confirmation {"password"} end end
spec/factories/tasks.rb
FactoryBot.define do factory :task do sequence(:title, "title1") content { "content" } status { :todo } deadline { 1.week.from_now } association :user end end
association :userでuserと関連付けできる。
この記載をしないとuser_idが採番できず、buildの段階では問題ないが、createなどDBに保存しようとするときエラーが出る。
↓buildの場合
validとerrorsの順番
valid -> errorsの順番でテストしないとerrorsのerrorの理由が表示されない。
validでfalseになった時に@messageなどが更新される。
バリデーションテスト
spec/models/task_spec.rb
require 'rails_helper' RSpec.describe Task, type: :model do # バリデーションについてテストするグループ describe 'validation' do # 全ての属性が有効 it 'is valid with all attributes' do task = build(:task) # taskが有効か expect(task).to be_valid # task.errorsでmessagesやdetailsが空欄になっているか expect(task.errors).to be_empty end # タイトルがなければ無効である it 'is invalid without title' do task_without_title = build(:task, title: "") expect(task_without_title).to be_invalid expect(task_without_title.errors[:title]).to eq ["can't be blank"] end # statusがなければ無効である it 'is invalid without status' do task_without_status = build(:task, status: nil) expect(task_without_status).to be_invalid expect(task_without_status.errors[:status]).to eq ["can't be blank"] end # titleが重複していたら無効である it 'is invalid with a duplicate title' do task = create(:task) task_with_duplicated_title = build(:task, title: task.title) expect(task_with_duplicated_title).to be_invalid expect(task_with_duplicated_title.errors[:title]).to eq ["has already been taken"] end # 他のtitleであれば有効(記載してもしなくてもいい) it 'is valid with another title' do task = create(:task) task_with_another_title = build(:task, title: 'another_title') expect(task_with_another_title).to be_valid expect(task_with_another_title.errors).to be_empty end end end
rails consoleでexpectの内容を確認したい時
例えばtitleが重複していたらinvalidになるというテストの場合、以下のように確認する。
参考・引用文献
factory_bot/GETTING_STARTED.md at master · thoughtbot/factory_bot · GitHub
スタブオブジェクト - Oracle® Solaris 11.3 リンカーとライブラリガイド
【RSpec初級編】FactoryBotを用いてテストコードを効率化する方法について解説|TechTechMedia
テストスイート(てすとすいーと):情報システム用語事典 - ITmedia エンタープライズ
Active Support コア拡張機能 - Railsガイド
いまさらですがActive Supportについて概要を説明します。 | collabit(コラビット)|不動産テック(RealEstateTech)企業
Active Record バリデーション - Railsガイド