STUDY MEMO

学習のメモ書き

<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つのフィクスチャファイルが作成される。
ファクトリとは?
データを作成することを簡単にする仕組み。
テストデータ構築用のブロック。
factory_botではサンプルデータのテンプレートファイルを指す。

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

Image from Gyazo

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の場合
Image from Gyazo Image from Gyazo

↓createの場合 Image from Gyazo

validとerrorsの順番

valid -> errorsの順番でテストしないとerrorsのerrorの理由が表示されない。
validでfalseになった時に@messageなどが更新される。 Image from Gyazo


バリデーションテスト

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になるというテストの場合、以下のように確認する。
Image from Gyazo

参考・引用文献

factory_bot/GETTING_STARTED.md at master · thoughtbot/factory_bot · GitHub

Rails テスティングガイド - Railsガイド

https://www.amazon.co.jp/%E7%8F%BE%E5%A0%B4%E3%81%A7%E4%BD%BF%E3%81%88%E3%82%8B-Ruby-Rails-5%E9%80%9F%E7%BF%92%E5%AE%9F%E8%B7%B5%E3%82%AC%E3%82%A4%E3%83%89-%E5%A4%A7%E5%A0%B4%E5%AF%A7%E5%AD%90/dp/4839962227/ref=sr_1_1?__mk_ja_JP=%E3%82%AB%E3%82%BF%E3%82%AB%E3%83%8A&dchild=1&keywords=rails+5&qid=1610940943&sr=8-1

スタブオブジェクト - Oracle® Solaris 11.3 リンカーとライブラリガイド

【RSpec初級編】FactoryBotを用いてテストコードを効率化する方法について解説|TechTechMedia

テストスイート(てすとすいーと):情報システム用語事典 - ITmedia エンタープライズ

Active Support コア拡張機能 - Railsガイド

いまさらですがActive Supportについて概要を説明します。 | collabit(コラビット)|不動産テック(RealEstateTech)企業

Active Record バリデーション - Railsガイド

Rails RSpecの準備とテストコード基礎 - Qiita

Active Record バリデーション - Railsガイド