はじめに
テストを全く記述したことがない状態から RSpec を実際に記述しながら学びました。理解した内容を実際に記述したコードとともに記事に残したいと思います。
※RSpec 初心者です。誤り、ご指摘などございましたらコメントいただければ非常にありがたいです。
前提
- Usersテーブル
- カラム:
email: string
とpassword: string
のみ - Booksテーブルと1対多の関連付けがされている
- カラム:
- テストデータの生成には Gem
FactoryBot
を使用 - テストには簡単に記述できる Gem
Shoulda Matchers
を使用
Shoulda Matchers
を使うと何行も記述しないとできなかったテストが1行で書けてしまいます。何が行われているかわからない部分もありますが、今回記述した内容の他にも便利な構文が用意されておりますので、興味のある方は参考サイトからぜひ確認してみることをお勧めします。
参考サイト
- 使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」
- 使えるRSpec入門・その2「使用頻度の高いマッチャを使いこなす」
- Shoulda Matchers
- FactoryBot(FactoryGirl)チートシート
- 【RSpec】で書く「Rails Tutorial 6章」のテスト
非常にわかりやすく大変参考にさせていただきました!
ありがとうございました!
Model
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つのテストを行っている
ファイル全体
# 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でテストデータを作成
ファクトリーボットで作成するテストデータを専用ファイルに定義
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.pry
でuser
とupcase_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>
※上記create
とbuild
はFactoryBot
の記述を省略できる方法を適用。
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 の学びはじめは構文に慣れておらず非常に苦労しました。しかし、わかりやすい解説の記事も多く、慣れてきて思った通りにテストがパスすると楽しくなってきます。
今回はモデルテストの記事を書きましたが、リクエストテストも学んだのでそちらも今後記事にできたらいいと思っています。