Udemy 8. ユーザーモデル開発 #07
2020年07月15日に公開

Railsユーザーモデルバリデーションテスティング(name/email/password)

今回達成すること

ユーザーモデルに設定したバリデーションが正しく動いているかテストを行います。

実装に入る前に、この回で設定したバリデーションをおさらいしておきましょう。

カラム 内容 入力 最小文字数 最大文字数 書式チェック 一意性
name ユーザー名 必須 30 しない なし
email メースアドレス 必須 255 する メソッド
password_digest パスワード 必須 8 72(bcryptの仕様) する なし
activated メール認証フラグ - - - -
admin 管理者フラグ - - - -

この表に沿ってテストを実行していきます。

共通ユーザーを作成する

まず今後のテストに使用する共通のユーザーをtest_helpter.rbに宣言しましょう。

api/test/test_helper.rb
...
class ActiveSupport::TestCase
  ...

  # 追加
  def active_user
    User.find_by(activated: true)
  end
end
  • active_user … ユーザーテーブルからアクティブなユーザーを一人取り出す。

共通ユーザーの宣言

user_test.rb内で使用するユーザーオブジェクトを宣言します。

api/test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase
  # 追加
  def setup
    @user = active_user
  end
end

このように、test_helpter.rbに記述したメソッドはテスト内のどこからでも呼び出せます。

コードを簡略化したい場合には積極的に使用しましょう。

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 … テストの失敗は0
  • 0 errors … エラーは0
  • 0 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↓

あなたの力になれること
私自身が独学でプログラミングを勉強してきたので、一人で学び続ける苦しみは痛いほど分かります。そこで、当時の私がこんなのあったら良いのにな、と思っていたサービスを立ち上げました。周りに質問できる人がいない、答えの調べ方が分からない、ここを聞きたいだけなのにスクールは高額すぎる...。そんな方に向けた単発・短期間メンターサービスを行っています。下のサービスへお進みください。
独学プログラマのサービス
次の記事はこちら
1. このカテゴリーの歩き方 #01
【お知らせ】UdemyでRails × Nuxt.jsの動画を公開することになりました
1. このカテゴリーの歩き方 #02
【随時更新】このカテゴリーで作るアプリケーションの仕様書
1. このカテゴリーの歩き方 #03
【随時更新】このカテゴリーの歩き方
1. このカテゴリーの歩き方 #04
(Docker+Rails6+Nuxt.js+PostgreSQL)=>Heroku 環境構築~デプロイまでの手順書
2. Docker入門 #01
Docker for Macをインストールする手順
2. Docker入門 #02
分かるDocker解説。仮想環境・コンテナ・Dockerイメージ・Dockerfileとは何か?
2. Docker入門 #03
分かるDocker解説。DockerComposeとは何か?
3. Dockerを使ったRails+Nuxt.js環境構築 #01
【Docker+Rails6+Nuxt.js】今回作成するアプリの開発環境の全体像を知ろう
3. Dockerを使ったRails+Nuxt.js環境構築 #02
【MacOS】Homebrew経由でGitをインストールする方法
3. Dockerを使ったRails+Nuxt.js環境構築 #03
Rails6を動かすAlpineベースのDockerfileを作成する(AlpineLinuxとは何か)
3. Dockerを使ったRails+Nuxt.js環境構築 #04
Nuxt.jsを動かすAlpineベースのDockerfileを作成する(C.UTF-8とは何か)
3. Dockerを使ったRails+Nuxt.js環境構築 #05
.envファイルを使ったdocker-compose.ymlの環境変数設計
3. Dockerを使ったRails+Nuxt.js環境構築 #06
Rails6・Nuxt.js・PostgreSQLを動かすdocker-compose.ymlファイルを作成する
3. Dockerを使ったRails+Nuxt.js環境構築 #07
docker-compose.ymlを使ってRails6を構築する(PostgreSQLパスワード変更方法)
3. Dockerを使ったRails+Nuxt.js環境構築 #08
docker-compose.ymlを使ってNuxt.jsを構築する
4. 複数プロジェクトのGit管理 #01
複数プロジェクトで行うGit管理の全体像を理解しよう(Gitサブモジュール解説)
4. 複数プロジェクトのGit管理 #02
【Git】既存の子ディレクトリをサブモジュール管理に変更する手順
4. 複数プロジェクトのGit管理 #03
【GitHub】秘密鍵の生成・公開鍵を追加・SSH接続するまでを画像で分かりやすく
4. 複数プロジェクトのGit管理 #04
【GitHub】リモートリポジトリの追加・サブモジュールのリンク設定を行う
5. RailsAPI×Nuxt.js初めてのAPI通信 #01
【Rails6】"Hello" jsonを返すコントローラを作成する
5. RailsAPI×Nuxt.js初めてのAPI通信 #02
【Nxut.js】axiosの初期設定を行う(baseURL・browserBaseURLを解説)
5. RailsAPI×Nuxt.js初めてのAPI通信 #03
【Rails6】Gem rack-corsを導入してCORS設定を行う(オリジン・CORSとは何か)
6. Heroku.ymlを使ったDockerデプロイ #01
デプロイ準備。Herokuへ新規会員登録を行いHerokuCLIをインストールする
6. Heroku.ymlを使ったDockerデプロイ #02
heroku.yml解説編。Docker環境のRails6をHerokuにデプロイする(1/2)
6. Heroku.ymlを使ったDockerデプロイ #03
HerokuCLI-manifestのデプロイ解説編。Docker環境のRails6をHerokuにデプロイする(2/2)
6. Heroku.ymlを使ったDockerデプロイ #04
Dockerfile解説編。Docker環境のNuxt.jsをHerokuにデプロイする(1/2)
6. Heroku.ymlを使ったDockerデプロイ #05
デプロイ完結編。Docker環境のNuxt.jsをHerokuにデプロイする(2/2)
7. モデル開発事前準備 #01
【Rails6】application.rbの初期設定(タイムゾーン・I18n・Zeitwerk)
7. モデル開発事前準備 #02
【Rails6】モデル開発に必要なGemのインストールとHirb.enableの自動化
7. モデル開発事前準備 #03
【Docker+Rails】A server is already running. Check /tmp/pids/server.pidエラーの対応
7. モデル開発事前準備 #04
【Docker】<none>タグのイメージを一括削除する & Rails .gitignoreの編集
8. ユーザーモデル開発 #01
Railsユーザーモデル作成。テーブル設計・ユーザー認証設計を理解する
8. ユーザーモデル開発 #02
Railsユーザーモデルのバリデーション設定(has_secure_password解説)
8. ユーザーモデル開発 #03
Railsバリデーションエラーメッセージの日本語化(ja.yml設定方法)
8. ユーザーモデル開発 #04
EachValidatorクラスのカスタムバリデーション設定(Rails6/lib以下読込)
8. ユーザーモデル開発 #05
Rails環境ごとにSeedデータ切り替えるseeds.rbの書き方
8. ユーザーモデル開発 #06
Rails6から導入された並列テストを理解する
8. ユーザーモデル開発 #07
Railsユーザーモデルバリデーションテスティング(name/email/password)
8. ユーザーモデル開発 #08
Nuxt.jsからRailsのユーザーテーブルを取得しHerokuにデプロイする
9. Nuxt.jsフロント開発事前準備 #01
【Nuxt.js2.13超解説】バージョンアップ手順と6つの新機能+2つの変更点
9. Nuxt.jsフロント開発事前準備 #02
Docker AlpineベースのNode.js上で動くNuxt.jsにVuetifyを導入する
9. Nuxt.jsフロント開発事前準備 #03
VuetifyにカスタムCSSを導入してオリジナルブレイクポイントを作る
9. Nuxt.jsフロント開発事前準備 #04
Nuxt.jsにnuxt-i18nを導入して国際化に対応する
10. 認証アプリのレイアウト設計 #01
Nuxt.jsのレイアウト・ページ・コンポーネントの役割を理解しよう
10. 認証アプリのレイアウト設計 #02
ウェルカムページを構成するコンポーネントファイル群を作成しよう(1/4)
新着
10. 認証アプリのレイアウト設計 #03
ウェルカムページにアイキャッチ画像・アプリ名・メニューボタンを表示しよう(2/4)
新着
10. 認証アプリのレイアウト設計 #04
addEventListenerでスクロールを検知しツールバーの色を変化させよう(3/4)
新着
10. 認証アプリのレイアウト設計 #05
ウェルカムページをレスポンシブデザインに対応させよう(4/4)
新着
SPA開発
ブログ構築
小ネタ集