LoginSignup
3
2

More than 3 years have passed since last update.

【Rails / RSpec】 Modelテストを書く(Shoulda Matchers・FactoryBot 使用)

Last updated at Posted at 2020-10-27

はじめに

テストを全く記述したことがない状態から RSpec を実際に記述しながら学びました。理解した内容を実際に記述したコードとともに記事に残したいと思います。

※RSpec 初心者です。誤り、ご指摘などございましたらコメントいただければ非常にありがたいです。

前提

  • Usersテーブル
    • カラム: email: stringpassword: stringのみ
    • Booksテーブルと1対多の関連付けがされている
  • テストデータの生成には Gem FactoryBotを使用
  • テストには簡単に記述できる Gem Shoulda Matchersを使用

Shoulda Matchersを使うと何行も記述しないとできなかったテストが1行で書けてしまいます。何が行われているかわからない部分もありますが、今回記述した内容の他にも便利な構文が用意されておりますので、興味のある方は参考サイトからぜひ確認してみることをお勧めします。

参考サイト

非常にわかりやすく大変参考にさせていただきました!
ありがとうございました!

Model

app/models/user.rb
class User < ApplicationRecord
  has_many :books # booksと1対多の関連付け
  has_secure_password
  before_save { email.downcase! }

  validates :email, presence: true,
                    length: { maximum: 255 },
                    format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i },
                    uniqueness: { case_sensitive: false }
  validates :password, presence: true,
                       length: { minimum: 6 }
end

テスト

合計9つのテストを行っている

ファイル全体

spec/models/user_spec.rb
# rails_helperファイルを読み込み
require 'rails_helper'

# Userモデルのテストなので User, type: :model としている
RSpec.describe User, type: :model do

  describe 'has_many' do
    # has_many :books のテスト
    it { should have_many(:books) }
  end

  describe 'has_secure_password' do
    # has_secure_password のテスト
    it { should have_secure_password }
  end

  describe 'validation' do
    context 'email' do
      # FactoryBot でテストデータを作成
      let(:user) { create(:user) }
      let(:upcase_email_user) { build(:user, :upcase_email) }

      # presence: true のテスト
      it { should validate_presence_of(:email) }
      # length: { maximum: 255 } のテスト
      it { should validate_length_of(:email).is_at_most(255) }

      # uniqueness: { case_sensitive: false } のテスト
      it 'emailの重複保存を許容しないこと' do
        duplicate_user = user.dup
        duplicate_user.email = user.email.upcase
        expect(duplicate_user).to be_invalid
      end

      # format: { with: XXXXX } のテスト
      it '指定formatに合わないemailを許容しないこと' do
        invalid_emails = %w[user@foo,com user_at_foo.org example.user@foo.foo@bar_baz.com foo@bar+baz.com foo@bar..com]
        invalid_emails.each do |invalid_email|
          user.email = invalid_email
          expect(user).to be_invalid
        end
      end

      # before_save { email.downcase! } のテスト
      it 'downcase!が正常に動作していること' do
        upcase_email_user.save!
        expect(upcase_email_user.email).to eq 'test@test.com'
      end
    end

    context 'password' do
      # presence: true のテスト
      it { should validate_presence_of(:password) }
      # length: { minimum: 6 } のテスト
      it { should validate_length_of(:password).is_at_least(6) }
    end
  end
end

各記述の解説

テストの宣言

RSpec.describe モデル名, type: :テスト形式 doとなっており、この記述でブロック内でのUserモデルのテストが実行できる

RSpec.describe User, type: :model do

has_many と has_secure_password のテスト

Shoulda Matchersを利用してモデルの関連付けと、 has_secure_password の適用を検証

# has_many :books のテスト
describe 'has_many' do
  it { should have_many(:books) }
end

# has_secure_password のテスト
describe 'has_secure_password' do
  it { should have_secure_password }
end

describeとcontext でテスト記述を分ける

describe 'validation' do
    context 'email' do
      # emailのバリデーションテストを記述
    end
    context 'password' do
      # passwordのバリデーションテストを記述
    end
end

FactoryBotでテストデータを作成

ファクトリーボットで作成するテストデータを専用ファイルに定義

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    # データを生成する毎に通し番号をふってユニークな値を作るようにしている
    sequence(:email) { |n| "test#{n}@test.com" }
    password { 'password' }

    trait :upcase_email do
      email { 'TeSt@TEst.Com' }
    end
  end
end

上記ファイルを元にletでテストデータを作成しており、このテストデータは記述したブロック内のit .. do ~~ endブロックで使用できる。
コメントアウトでbinding.pryuserupcase_email_userの中身を確認した結果を記載している。

  let(:user) { create(:user) }
  # user = <User:0x0000560469431138 id: 2610, email: "test1@test.com", password_digest: "[FILTERED]", created_at: Tue, 27 Oct 2020 14:03:04 JST +09:00, updated_at: Tue, 27 Oct 2020 14:03:04 JST +09:00>
  let(:upcase_email_user) { build(:user, :upcase_email) }
  # upcase_email_user = <User:0x00005604690c12c8 id: nil, email: "TeSt@TEst.Com", password_digest: "[FILTERED]", created_at: nil, updated_at: nil>

※上記createbuildFactoryBotの記述を省略できる方法を適用。

email バリデーション

presence:true length: { maximum: 255 } のテスト(Shoulda Matchers)

Shoulda Matchersを用いてテストを記述

  # presence: true をテスト
  it { should validate_presence_of(:email) }
  # length: { maximum: 255 } をテスト
  it { should validate_length_of(:email).is_at_most(255) }

uniqueness: { case_sensitive: false }のテスト

dupメソッドuserのコピーデータを作成。
let(:user) { create(:user) }ですでに登録済みのuserと同じemailを持つduplicate_userが保存されないことを検証。
テストの前にemailをupcaseで大文字にすることで、大文字でも同一なemailと判定するかも合わせて検証している。

be_invalidはバリデーションで引っかかれば(エラーが出れば)パスする

it 'emailの重複保存を許容しないこと' do
  duplicate_user = user.dup
  # この時点での中身
  # duplicate_user = <User:0x000055f1e1d71e90 id: nil, email: "test1@test.com", password_digest: "[FILTERED]", created_at: nil, updated_at: nil>
  duplicate_user.email = user.email.upcase
  # user.email.upcase後の中身
  # #<User:0x000055f1e1d71e90 id: nil, email: "TEST1@TEST.COM", password_digest: "[FILTERED]", created_at: nil, updated_at: nil>

  expect(duplicate_user).to be_invalid  # duplicate_user.invalid? が true になればパスする
end

format: { with: /\A[\w+-.]+@[a-z\d-]+(.[a-z\d-]+)*.[a-z]+\z/i } のテスト

it '指定formatに合わないemailを許容しないこと' do
  # 指定formatに合致しない文字列の配列を6つ用意
  invalid_emails = %w[user@foo,com user_at_foo.org example.user@foo.foo@bar_baz.com foo@bar+baz.com foo@bar..com]
  # userのemailにformatに合致しないemailをいれて検証
  invalid_emails.each do |invalid_email|
    user.email = invalid_email
    expect(user).to be_invalid # duplicate_user.invalid? が true になればパスする
  end
end

before_save { email.downcase! } のテスト

upcase_email_userのemail TeSt@TEst.Com がsave後にはtest@test.comになっていることを検証しており、
expect(X).to eq Yは「XがYに等しくなること」をテストしている。

it 'downcase!が正常に動作していること' do
  # before_saveはsaveの直前に呼び出されるためsave!する
  upcase_email_user.save!
  expect(upcase_email_user.email).to eq 'test@test.com'
end

password バリデーション

presence: true、 length: { maximum: 6 } のテスト(Shoulda Matchers)

emailと同様にShoulda Matchersを用いてテストを記述

context 'password' do
  # presence: true のテスト
  it { should validate_presence_of(:password) }
  # length: { maximum: 6 } のテスト
  it { should validate_length_of(:password).is_at_least(6) }
end

最後に

RSpec の学びはじめは構文に慣れておらず非常に苦労しました。しかし、わかりやすい解説の記事も多く、慣れてきて思った通りにテストがパスすると楽しくなってきます。
今回はモデルテストの記事を書きましたが、リクエストテストも学んだのでそちらも今後記事にできたらいいと思っています。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2