はじめに
Railsでよく見るバリデーションのテストをRSpecで書きました。
テスト内容
今回は、name, user_id, email を持つUserモデルを作成しました。
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.string :name
t.string :user_id
t.string :email
t.timestamps
end
end
end
Userモデルのテストでは、主にバリデーションのテストを行いました。
class User < ApplicationRecord
before_save { email.downcase! }
validates :name, presence: true,
length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
validates :email, presence: true,
length: { maximum: 255 },
uniqueness: { case_sensitive: false },
format: { with: VALID_EMAIL_REGEX }
end
バリデーションの設定に関しては、Railsチュートリアルを参考にしています。バリデーションの設定や意味などは、そちらを参考に。
環境設定
Gemfile に以下のように追記します。
group :development, :test do
# 省略
gem 'factory_bot_rails'
gem 'rspec-rails'
# 省略
end
そうすると、Userモデル作成と同時にRSpecのファイルが生成されます。
$ rails g model User ...
Running via Spring preloader in process 19654
invoke active_record
create db/migrate/20190617xxxxxx_create_userss.rb
create app/models/user.rb
invoke rspec
create spec/models/user_spec.rb
なので、テストは以下に書き込めばいいと。
spec/models/user_spec.rb
テスト作成
以下が、Userモデルに対するすテスト(RSpec)
require 'rails_helper'
RSpec.describe User, type: :model do
before do
@user = User.create(
name: "Yamada Taro",
user_id: "taro",
email: "taro@example.com",
)
end
# name、user_id、emailがあれば有効な状態であること
it "is valid with a name, user_id, email" do
user = User.new(
name: "Sato Taro",
user_id: "sato",
email: "sato@example.com",
)
expect(user).to be_valid
end
# nameがなければ無効な状態であること(nameは必須項目)
it "is invalid without a name" do
user = User.new(name: nil)
user.valid?
expect(user.errors[:name]).to include("can't be blank")
end
# メールアドレスがなければ無効な状態であること(emailは必須項目)
it "is invalid without an email address" do
user = User.new(email: nil)
user.valid?
expect(user.errors[:email]).to include("can't be blank")
end
# 重複したメールアドレスなら無効な状態であること
it "is invalid with a duplicate email address" do
user = User.new(email: "taro@example.com")
user.valid?
expect(user.errors[:email]).to include("has already been taken")
end
# メールアドレスが小文字化されていること
it "email addresses should be saved as lower-case" do
mixed_case_email = "TANAKA@example.com"
@user.email = mixed_case_email
@user.save
expect(@user[:email]).to eq("tanaka@example.com")
end
# メールアドレスの一意性の検証
it "email is unique" do
user = User.create(name: "Test", user_id: "test", email: "taro@example.com")
expect(user).to_not be_valid
expect(user.errors[:email]).to include("has already been taken")
end
# メールアドレスのフォーマットの検証
describe "mail" do
context "Correct format" do
it "is OK" do
@user.email = 'user@example.com'
expect(@user).to be_valid
end
end
context "Incorrect format" do
it "is NG" do
@user.email = 'user@example,com'
expect(@user).to_not be_valid
expect(@user.errors[:email]).to include("is invalid")
end
end
end
# nameの長さの検証
describe "name length" do
context "length 50 txt" do
it "is OK" do
@user.name = 'a' * 50
expect(@user).to be_valid
end
end
context "length 51 txt" do
it "is NG" do
@user.name = 'a' * 51
expect(@user).to_not be_valid
expect(@user.errors[:name]).to include("is too long (maximum is 50 characters)")
end
end
end
end
1ブロックづつ説明します
重複のチェックの際に使用するので、事前にテスト対象のオブジェクトとしてインスタンス化しておきます。
before do
@user = User.create(
name: "Yamada Taro",
user_id: "taro",
email: "taro@example.com",
)
end
name、user_id、emailがあれば有効な状態であること
# name、user_id、emailがあれば有効な状態であること
it "is valid with a name, user_id, email" do
user = User.new(
name: "Sato Taro",
user_id: "sato",
email: "sato@example.com",
)
expect(user).to be_valid
end
expect(user).to be_valid
は、user
が妥当(valid)なことを期待するという意味です。
RSpecではこうしたエクスペクテーション(expect)を書くため、to
やto_not
を用いています。
nameがなければ無効な状態であること(nameは必須項目)
it "is invalid without a name" do
user = User.new(name: nil)
user.valid?
expect(user.errors[:name]).to include("can't be blank")
end
テストでは、user.name
にエラーメッセージ("can't be blank"
)が付いてることを期待(expect)しています。
メールアドレスがなければ無効な状態であること(emailは必須項目)
it "is invalid without an email address" do
user = User.new(email: nil)
user.valid?
expect(user.errors[:email]).to include("can't be blank")
end
valid? が false
なら、errors.messages
にエラー内容が格納されます。今回は、それが、"can't be blank"
であることを期待(expect)しています。
重複したメールアドレスなら無効な状態であること
it "is invalid with a duplicate email address" do
user = User.new(email: "taro@example.com")
user.valid?
expect(user.errors[:email]).to include("has already been taken")
end
valid? が false
なら、errors.messages
にエラー内容が格納されます。今回は、それが、"has already been taken"
であることを期待(expect)しています。
メールアドレスが小文字化されていること
it "email addresses should be saved as lower-case" do
mixed_case_email = "TANAKA@example.com"
@user.email = mixed_case_email
@user.save
expect(@user[:email]).to eq("tanaka@example.com")
end
@user[:email]
の値 = "tanaka@example.com"
がTRUE
であることを期待(expect)しています。
メールアドレスの一意性の検証
it "email is unique" do
user = User.create(name: "Test", user_id: "test", email: "taro@example.com")
expect(user).to_not be_valid
expect(user.errors[:email]).to include("has already been taken")
end
taro@example.com
はすでに保存されたアドレスなので、バリデーションは通らないことと、エラーメッセージは"has already been taken"
であることを期待(expect)しています。
メールアドレスのフォーマットの検証
-
,com
のカンマがあるのは、無効である -
.com
であれば、有効である(すくなくとも無効ではない)
ことを
User
モデルのバリデーション部分に記載しているので、テストがパスしないことを期待(expect)しています。
describe "mail" do
context "Correct format" do
it "is OK" do
@user.email = 'user@example.com'
expect(@user).to be_valid
end
end
context "Incorrect format" do
it "is NG" do
@user.email = 'user@example,com'
expect(@user).to_not be_valid
expect(@user.errors[:email]).to include("is invalid")
end
end
end
nameの長さの検証
name
の中身の文字数が
- 50文字までは有効
- 51文字以上は無効
であることをUser
モデルのバリデーション部分に記載しているので、テストがパスしないことを期待(expect)しています。
describe "name length" do
context "length 50 txt" do
it "is OK" do
@user.name = 'a' * 50
expect(@user).to be_valid
end
end
context "length 51 txt" do
it "is NG" do
@user.name = 'a' * 51
expect(@user).to_not be_valid
expect(@user.errors[:name]).to include("is too long (maximum is 50 characters)")
end
end
end
テスト実行結果
bundle exec rspec spec
で実行できます。
$ bundle exec rspec spec
StaticPagesController
GET #top
returns http success
GET #about
returns http success
UsersController
GET #new
returns http success
User
is valid with a name, user_id, email
is invalid without a name
is invalid without an email address
is invalid with a duplicate email address
email addresses should be saved as lower-case
email is unique
mail
Correct format
is OK
Incorrect format
is NG
name length
length 50 txt
is OK
length 51 txt
is NG
Finished in 0.34401 seconds (files took 1.19 seconds to load)
13 examples, 0 failures
全て通りました。
まとめ
- テストとは基本的に、期待する値と、実際に生成される値を比較検証であると実感
- テストの妥当性を検証するために、期待する値を変更したりして、テストをパスしないことを確かめる必要性を実感
-
it ... end
中にあるブロックは、1つのテストをまとめる役割をしています。これによって、テストがパスしなかった場合、どこでコケたか、原因究明がわかりやすくなる - テストをパスするということは、デプロイのたびにビクビクせず、自信を持てると実感
まだまだ勉強中なので、問題点があればご指摘ください。