今回達成すること
ユーザーモデルに設定したバリデーションが正しく動いているかテストを行います。
実装に入る前に、この回で設定したバリデーションをおさらいしておきましょう。
カラム | 内容 | 入力 | 最小文字数 | 最大文字数 | 書式チェック | 一意性 |
---|---|---|---|---|---|---|
name | ユーザー名 | 必須 | 30 | しない | なし | |
メースアドレス | 必須 | 255 | する | メソッド | ||
password_digest | パスワード | 必須 | 8 | 72(bcryptの仕様) | する | なし |
activated | メール認証フラグ | - | - | - | - | |
admin | 管理者フラグ | - | - | - | - |
この表に沿ってテストを実行していきます。
共通ユーザーを作成する
まず今後のテストに使用する共通のユーザーを
api/test/test_helper.rb
...
class ActiveSupport::TestCase
...
# 追加
def active_user
User.find_by(activated: true)
end
end
active_user
... ユーザーテーブルからアクティブなユーザーを一人取り出す。
共通ユーザーの宣言
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
# 追加
def setup
@user = active_user
end
end
このように、
コードを簡略化したい場合には積極的に使用しましょう。
user.nameバリデーションテスト
チェックする内容
- 入力必須
- 最大30文字まで
入力必須をテストする
まずテスト項目のname_validation
ブロックを作成し、その中でユーザーオブジェクトを作成・保存します。
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
# 追加
test "name_validation" do
# 入力必須
user = User.new(email: "test@example.com", password: "password")
user.save
end
end
このユーザーはname
を空の状態で保存したので「名前を入力してください」というエラーメッセージが期待されます。
バリデーションエラーは、配列で返されるので期待されるエラーメッセージを配列で宣言しましょう。
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
test "name_validation" do
# 入力必須
user = User.new(email: "test@example.com", password: "password")
user.save
required_msg = ["名前を入力してください"] # 追加
end
end
最後にRailsのテストメソッド、assert_equal(アサート イコール)
を実行します。
第一引数と第二引数が一致した場合にtrue
を返しテストが通ります。
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
test "name_validation" do
# 入力必須
user = User.new(email: "test@example.com", password: "password")
user.save
required_msg = ["名前を入力してください"]
assert_equal(required_msg, user.errors.full_messages) # 追加
end
end
user.errors.full_messages
... バリデーションのエラーメッセージを取得する。
これで入力必須バリデーションのテストが完成しました。実行してみましょう。
root $ docker-compose run --rm api rails t
...
1 tests, 1 assertions, 0 failures, 0 errors, 0 skips
1 tests
... 1つのテストブロックの1 assertions
... 1つのassert
を実行した結果、0 failures
... テストの失敗は00 errors
... エラーは00 skips
... 飛ばしたテストは0
と読みます。
30文字制限をテストする
考え方は同じです。
31文字の名前を用意し保存して見た場合、エラーメッセージは正しく出力できているかを確認しています。
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
test "name_validation" do
...
# 追加
# 文字数制限
max = 30
name = "a" * (max + 1)
user.name = name
user.save
maxlength_msg = ["名前は30文字以内で入力してください"]
assert_equal(maxlength_msg, user.errors.full_messages)
end
end
逆に30文字は保存できているか
念のため成功事例の場面もテストします。
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
test "name_validation" do
...
# 追加
# 30文字以内は正しく保存されているか
name = "あ" * max
user.name = name
assert_difference("User.count", 1) do
user.save
end
end
end
assert_difference("User.count", 1)
assert_difference(アサート ディファレンス)
は、ブロック内の処理を実行した結果、第一引数が第二引数の数だけ変化していればテストが通ります。
ユーザーが正しく保存できた場合、User.count
が1増えるはずなので第二引数に「+1」を渡しています。
user.nameのテストを実行してみる
以上でname
のバリデーションテストが完了です。実行してみましょう。
root $ docker-compose run --rm api rails t
...
1 tests, 3 assertions, 0 failures, 0 errors, 0 skips
うまくいきました!
以下省略。このコマンドは適時実行してください。
user.emailバリデーションテスト
チェックする内容
- 入力必須
- 最大255文字まで
- 正しい書式は保存できているか
- 間違った書式はエラーを吐いているか
入力必須
新たなテストブロックemail_validation
を作成し、その中にテストを書いていきます。
内容が重複する説明は飛ばします。
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
# 追加
test "email_validation" do
# 入力必須
user = User.new(name: "test", password: "password")
user.save
required_msg = ["メールアドレスを入力してください"]
assert_equal(required_msg, user.errors.full_messages)
end
end
文字数制限
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
test "email_validation" do
...
# 追加
# 文字数制限
max = 255
domain = "@example.com"
email = "a" * ((max + 1) - domain.length) + domain
assert max < email.length
user.email = email
user.save
maxlength_msg = ["メールアドレスは255文字以内で入力してください"]
assert_equal(maxlength_msg, user.errors.full_messages)
end
end
正しい書式は保存できているか
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
test "email_validation" do
...
# 追加
# 書式チェック format = /\A\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\z/
ok_emails = %w(
A@EX.COM
a-_@e-x.c-o_m.j_p
a.a@ex.com
a@e.co.js
1.1@ex.com
a.a+a@ex.com
)
ok_emails.each do |email|
user.email = email
assert user.save
end
end
end
-
assert user.save
... 引数がtrue
の場合にテストを通す。user.save
が成功した場合はtrue
が返ってきます。
間違った書式はエラーを吐いているか
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
test "email_validation" do
...
# 追加
ng_emails = %w(
aaa
a.ex.com
メール@ex.com
a~a@ex.com
a@|.com
a@ex.
.a@ex.com
a@ex.com
A@ex.com
a@?,com
1@ex.com
"a"@ex.com
a@ex@co.jp
)
ng_emails.each do |email|
user.email = email
user.save
format_msg = ["メールアドレスは不正な値です"]
assert_equal(format_msg, user.errors.full_messages)
end
end
end
email小文字化テスト
ユーザーモデルにはバリデーション実行前にメールアドレスを小文字化するメソッドがあります。
api/app/models/user.rb
before_validation :downcase_email
このメソッドが正しく動いているかテストをしておきましょう。
api/test/models/user_test.rb
class UserTest < ActiveSupport::TestCase
...
# 追加
test "email_downcase" do
# email小文字化テスト
email = "USER@EXAMPLE.COM"
user = User.new(email: email)
user.save
assert user.email == email.downcase
end
end
assert user.email == email.downcase
バリデーション実行後のユーザーメールアドレスと、小文字にしたemail
が一致していればテストが通ります。
アクティブユーザーの一意性テスト
チェックする内容
- アクティブユーザーがいない場合、同じメールアドレスが登録できているか
- ユーザーがアクティブになった場合、バリデーションエラーを吐いているか
- アクティブユーザーがいなくなった場合、ユーザーは保存できているか
- 最終的に一意性は保たれているか
前回までのおさらい
この回でアクティブなユーザーのメールアドレスが重複しないようemail_activated?
メソッドを作成しました。
api/app/models/user.rb
# 自分以外の同じemailのアクティブなユーザーがいる場合にtrueを返す
def email_activated?
users = User.where.not(id: id)
users.find_activated(email).present?
end
このメソッドは、
- ユーザーオブジェクトの
email
が - 既にユーザーテーブルに存在し、かつ
- そのユーザーが
activated: true
の場合
true
を返すメソッドです。
そしてこのメソッドがtrue
の場合に、バリデーションエラーとなります。
api/lib/validator/email_validator.rb
record.errors.add(attribute, :taken) if record.email_activated?
これらが正しく動いているかを検証していきます。
アクティブユーザーがいない場合
何度でも同じメールアドレスが登録できているかを検証します。
これは、メール認証の期限切れユーザーに対応するものです。
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
# 追加
test "active_user_uniqueness" do
email = "test@example.com"
# アクティブユーザーがいない場合、同じメールアドレスが登録できているか
count = 3
assert_difference("User.count", count) do
count.times do |n|
User.create(name: "test", email: email, password: "password")
end
end
end
end
ユーザーがアクティブになった場合
ユーザーがアクティブになった場合、もう同じメールアドレスは登録できません。
バリデーションエラーを吐いているか検証しましょう。
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
test "active_user_uniqueness" do
...
# 追加
# ユーザーがアクティブになった場合、バリデーションエラーを吐いているか
active_user = User.find_by(email: email)
active_user.update!(activated: true)
assert active_user.activated
assert_no_difference("User.count") do
user = User.new(name: "test", email: email, password: "password")
user.save
uniqueness_msg = ["メールアドレスはすでに存在します"]
assert_equal(uniqueness_msg, user.errors.full_messages)
end
end
end
assert_no_difference("User.count")
assert_difference
とは逆のテストメソッドです。
ブロック内の処理を実行した結果、引数の数に変化がなければテストを通します。
つまりユーザーが保存されていないことをテストしています。
アクティブなユーザーがいなくなった場合
アクティブユーザーが削除された後の事も考えておきましょう。
正しく保存されているかテストします。
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
test "active_user_uniqueness" do
...
# 追加
# アクティブユーザーがいなくなった場合、ユーザーは保存できているか
active_user.destroy!
assert_difference("User.count", 1) do
User.create(name: "test", email: email, password: "password", activated: true)
end
end
end
一意性は保たれているか
最後に一意性は保たれているかをテストします。
アクティブユーザーの数が1人の場合だとOKですね。
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
test "active_user_uniqueness" do
...
# 追加
# 一意性は保たれているか
assert_equal(1, User.where(email: email, activated: true).count)
end
end
これでアクティブユーザーの一意性テストが完了です。
user.passwordバリデーションテスト
チェックする内容
- 入力必須
- 8文字以上
- 最大72文字まで
- 書式チェック
パスワードはメールアドレスと同じようなテストとなります。
api/test/models/user_test.rb
require 'test_helper'
class UserTest < ActiveSupport::TestCase
...
# 追加
test "password_validation" do
# 入力必須
user = User.new(name: "test", email: "test@example.com")
user.save
required_msg = ["パスワードを入力してください"]
assert_equal(required_msg, user.errors.full_messages)
# min文字以上
min = 8
user.password = "a" * (min - 1)
user.save
minlength_msg = ["パスワードは8文字以上で入力してください"]
assert_equal(minlength_msg, user.errors.full_messages)
# max文字以下
max = 72
user.password = "a" * (max + 1)
user.save
maxlength_msg = ["パスワードは72文字以内で入力してください"]
assert_equal(maxlength_msg, user.errors.full_messages)
# 書式チェック VALID_PASSWORD_REGEX = /\A[\w\-]+\z/
ok_passwords = %w(
pass---word
________
12341234
____pass
pass----
PASSWORD
)
ok_passwords.each do |pass|
user.password = pass
assert user.save
end
ng_passwords = %w(
pass/word
pass.word
|~=?+"a"
12345678
ABCDEFGH
password@
)
format_msg = ["パスワードは半角英数字•ハイフン•アンダーバーが使えます"]
ng_passwords.each do |pass|
user.password = pass
user.save
assert_equal(format_msg, user.errors.full_messages)
end
end
end
以上でバリデーションテストが実装できました。
テストコマンドを実行して確認しておいてください。
root $ docker-compose run --rm api rails t
...
5 tests, 47 assertions, 0 failures, 0 errors, 0 skips
コミットする
以上でユーザーモデルのバリデーションテストを終了します。
ここまでの変更をコミットしておきましょう。
root $ cd api
api $ git commit -am "add_user_model_test"
api $ cd ..
root $
まとめ
今回はユーザーモデルに追加したバリデーションが正しく動いているかテストを実行しました。
- user.name
- 入力必須
- 30文字まで
- user.email
- 入力必須
- 255文字まで
- 書式チェック
- メールアドレスが小文字になっているか
- アクティブユーザーの一意性テスト
- user.password
- 入力必須
- 8文字以上
- 72文字まで
- 書式チェック
あ...。共通ユーザー使わなかったね。
次回予告
さて次回は、ユーザーテーブルを本番環境に表示していきます。
Herokuへのユーザーテーブルデプロイ方法を学びましょう。
▶︎ 次の記事へGO↓