【Rails】ユーザーモデルのバリデーションをテストする
  • 2019.11.17に公開
  • SPA開発
  • 7. Userモデル開発
  • No.6 / 6
「SPA開発」では、Nuxt.js v2.14未満が使用されています。 Nuxt.jsはv2.13~14で大きなバージョンアップがありました。それに伴い、書き方も大きく変化しています。 v2.14以上で書かれた「SPA開発」の続編は、カテゴリー「 Udemy 」 で公開しています。

今回達成すること

ユーザーモデルで設定したバリデーションが正しく機能しているか、Railsのテスト機能を使って検証していきます。

今回テストする項目

ユーザーモデルのテスト項目は以下のとおりです。

email保存状態のテスト

  • emailが小文字で保存されているか

emailバリデーションテスト

  • 空の場合エラーになっているか(入力必須)
  • 256文字以上の場合エラーになっているか
  • 不正な書式にエラーを出しているか

email一意性テスト

  • 同じemailのユーザーは何人でも保存できているか
  • 同じemailでかつ、アクティブなユーザーが既に存在しているときにエラーを出しているか

passwordバリデーションテスト

  • 空の場合エラーになっているか(入力必須)
  • 8文字以下のパスワードにエラーを出しているか
  • 72文字を超える場合はエラーを出しているか
  • 不正な書式はエラーになっているか

この記事の読み方

最終的なuser_test.rbは一番下に記載しています。

最終的なuser_test.rb

以下、全てuser_test.rbを編集していきます。

編集の都度Railsのテストコマンドを実行し、テストが通ることを確認した上で次に進むことをおすすめします。

myapp $ rais t #もしくは $ rails test

それでは実装に入りましょう。

email保存状態のテスト

emailが小文字で保存されているか?

test/models/user_test.rb
test "email_downcase" do
  # emailが小文字で保存されているか
  email = "USER@EXAMPLE.COM"
  user = User.create(email: email, password: "password")
  assert user.email == email.downcase
end
  • assert

    引数に渡した式がtrueであるか判定します。

emailバリデーションテスト

空の場合にエラーになっているか?(入力必須)

test/models/user_test.rb
test "email_validator" do
  user = User.new(password: "password")
  # 入力必須
  user.save
  required = ["メールアドレスを入力してください"]
  assert_equal(required, user.errors.full_messages)
end
  • assert_equal(期待値, 実際値)

    期待値と実際値が等しいかを判定します。

    この場合、用意されたエラーメッセージと実際のエラーメッセージが等しいかを判定しています。

文字数が256文字以上の場合エラーになっているか

test/models/user_test.rb
test "email_validator" do
	...

  # OK 255文字以下
  ok_email = "#{"a" * (255 - 12)}@example.com"
  user.email = ok_email
  assert user.save

  # NG 255文字超
  user.email = ok_email + "a"
  user.save
  length = ["メールアドレスは255文字以内で入力してください"]
  assert_equal(length, user.errors.full_messages)
end

不正な書式にエラーを出しているか

  1. ハイフンとアンダーバー、プラスの記号が混ざったemailはバリデーションを通っているか?
  2. その他の記号、大文字、@が抜けている場合はエラーとなっているか?
test/models/user_test.rb
test "email_validator" do
	...

  # OK 書式チェック
  # /\A\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\z/
  oks = %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
  )
  oks.each do |ok|
    user.email = ok
    assert user.save
  end

  # NG 書式チェック
  ngs = %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
  )
  format = ["メールアドレスの書式が正しくありません"]
  ngs.each do |ng|
    user.email = ng
    user.save
    assert_equal(format, user.errors.full_messages)
  end
end

email一意性テスト

同じemailのユーザーは何人でも保存できているか?

test/models/user_test.rb
test "email_uniqueness" do
  # アクティブなユーザーがいない場合に同じユーザーは保存できているか
  10.times do |n|
    user = User.new(email: "user@example.com", password: "passowrd")
    assert user.save
  end
end

アクティブなユーザーが既に存在しているときにエラーを出しているか

  1. アクティブプラグがtrueのユーザーがいるときは、同じemailでは保存できないようになっているか?
  2. また、そのemailが大文字の場合でもエラーを吐いているか?
  3. アクティブなユーザーが存在しなくなったら、同じemailで保存できているか?
test/models/user_test.rb
test "email_uniqueness" do
  ...

  assert @user.activated
  user = @user.dup

  # NG アクティブなユーザーが既に存在している場合
  user.save
  unique = ["メールアドレスはすでに存在します"]
  assert_equal(unique, user.errors.full_messages)

  # NG emailが大文字でもエラーを出しているか
  user.email = @user.email.upcase
  user.save
  assert_equal(unique, user.errors.full_messages)
  
  # OK アクティブなユーザーがいなくなった場合
  @user.update(activated: false)
  assert user.save
end
  • user = @user.dup

dup はオブジェクトのコピーを作成するRubyのメソッドです。

dup は保存前の値だけをコピーするのに対して、 clone は、idやcreated_at(作成時間)までコピーします。

  • dup でコピーしたユーザーオブジェクト

    2019-11-17 14-00-37

  • cloneでコピーしたユーザーオブジェクト

    2019-11-17 14-00-38

passwordバリデーションテスト

空の場合エラーになっているか?(入力必須)

test/models/user_test.rb
test "password_validator" do
  user = User.new(email: "user@example.com")

  # 入力必須
  user.save
  required = ["パスワードを入力してください"]
  assert_equal(required, user.errors.full_messages)
end

8文字以下のパスワードにエラーを出しているか?

test/models/user_test.rb
test "password_validator" do
  ...
    
  # NG 8文字未満
  user.password = "a" * 7
  user.save
  min_length = ["パスワードは8文字以上で入力してください"]
  assert_equal(min_length, user.errors.full_messages)
end

72文字を超える場合はエラーを出しているか?

test/models/user_test.rb
test "password_validator" do
  ...
    
  # NG 72文字超
  user.password = "a" * 73
  user.save
  max_length = ["パスワードは72文字以内で入力してください"]
  assert_equal(max_length, user.errors.full_messages)

  # ok 72文字以下
  user.password = "a" * 72
  assert user.save
end

不正な書式はエラーになっているか

test/models/user_test.rb
test "password_validator" do
  ...
    
  # NG 書式チェック
  # VALID_PASSWORD_REGEX = /\A[\w\-]+\z/
  ngs = %w(pass/word pass.word |~=?+"a" 12345678 ABCDEFGH)
  format = ["パスワードは半角英数字、ハイフン、アンダーバーが使えます"]
  ngs.each do |ng|
    user.password = ng
    user.save
    assert_equal(format, user.errors.full_messages)
  end
end

全ての編集が完了したらテストコマンドを実行してエラーが出なければOKです。

myapp $ rails t
...
5 tests, 47 assertions, 0 failures, 0 errors, 0 skips

本番環境にpushする

ブランチを作成している場合はmasterブランチにマージします。

myapp $ git commit -am "user_validate_test" 
myapp $ git checkout master
myapp $ git merge user_modeling
myapp $ git push
myapp $ git push heroku

まとめ

これにてユーザーモデル開発は終了です。

今回のユーザーモデル開発は、

  1. マイグレーションファイルの作成
  2. $ rails db:migrate を実行してテーブルの作成
  3. バリデーションの設定
  4. seedデータの投入
  5. バリデーションテスト

の流れで一連の作業を行いました。(4と5は逆でもOK)

今後もこの流れを覚えておくと良きです。

さて、次回は?

さて、次回は。。。

今悩んでいるのですが、Railsチュートリアルに従ってユーザーログイン機能の実装をしましょうか。

はたまた、モデルを一通り作ってしまうか。。。

ログイン機能の場合、JWT認証を使って実装していきます。

更新をご期待ください。(歯切れが悪くてすみません。)

最終的なuser_test.rb

test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    init
  end

  test "sqlite3_test" do
    assert_not_nil @user
  end

  test "email_downcase" do
    # emailが小文字で保存されているか
    email = "USER@EXAMPLE.COM"
    user = User.create(email: email, password: "password")
    assert user.email == email.downcase
  end

  test "email_validator" do
    user = User.new(password: "password")
    # 入力必須
    user.save
    required = ["メールアドレスを入力してください"]
    assert_equal(required, user.errors.full_messages)

    # OK 255文字以下
    ok_email = "#{"a" * (255 - 12)}@example.com"
    user.email = ok_email
    assert user.save

    # NG 255文字超
    user.email = ok_email + "a"
    user.save
    length = ["メールアドレスは255文字以内で入力してください"]
    assert_equal(length, user.errors.full_messages)

    # OK 書式チェック
    # /\A\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*\z/
    oks = %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
    )
    oks.each do |ok|
      user.email = ok
      assert user.save
    end

    # NG 書式チェック
    ngs = %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
    )
    format = ["メールアドレスの書式が正しくありません"]
    ngs.each do |ng|
      user.email = ng
      user.save
      assert_equal(format, user.errors.full_messages)
    end
  end

  test "email_uniqueness" do
    # アクティブなユーザーがいない場合に同じユーザーは保存できているか
    10.times do |n|
      user = User.new(email: "user@example.com", password: "passowrd")
      assert user.save
    end

    assert @user.activated
    user = @user.dup

    # NG アクティブなユーザーが既に存在している場合
    user.save
    unique = ["メールアドレスはすでに存在します"]
    assert_equal(unique, user.errors.full_messages)

    # NG emailが大文字でもエラーを出しているか
    user.email = @user.email.upcase
    user.save
    assert_equal(unique, user.errors.full_messages)

    # OK アクティブなユーザーがいなくなった場合
    @user.update(activated: false)
    assert user.save
  end

  test "password_validator" do
    user = User.new(email: "user@example.com")

    # 入力必須
    user.save
    required = ["パスワードを入力してください"]
    assert_equal(required, user.errors.full_messages)

    # NG 8文字未満
    user.password = "a" * 7
    user.save
    min_length = ["パスワードは8文字以上で入力してください"]
    assert_equal(min_length, user.errors.full_messages)

    # NG 72文字超
    user.password = "a" * 73
    user.save
    max_length = ["パスワードは72文字以内で入力してください"]
    assert_equal(max_length, user.errors.full_messages)

    # ok 72文字以下
    user.password = "a" * 72
    assert user.save

    # NG 書式チェック
    # VALID_PASSWORD_REGEX = /\A[\w\-]+\z/
    ngs = %w(pass/word pass.word |~=?+"a" 12345678 ABCDEFGH)
    format = ["パスワードは半角英数字、ハイフン、アンダーバーが使えます"]
    ngs.each do |ng|
      user.password = ng
      user.save
      assert_equal(format, user.errors.full_messages)
    end
  end

end
あなたの力になれること
私自身が独学でプログラミングを勉強してきたので、一人で学び続ける苦しみは痛いほど分かります。そこで、当時の私がこんなのあったら良いのにな、と思っていたサービスを立ち上げました。周りに質問できる人がいない、答えの調べ方が分からない、ここを聞きたいだけなのにスクールは高額すぎる。そんな方に向けた単発・短期間メンターサービスを行っています。
独学プログラマのサービス
SPA開発の投稿
1
  • 更新情報
  • /
  • #01
「Rails apiとNuxt.jsでSPA開発」のデモアプリを開発中...。【2020/05/19追記: このカテゴリーの更新を一旦終了といたします】
1
  • 今回作るアプリケーション
  • /
  • #01
Railsアプリの完成イメージ画像と作ろうと思った経緯
2
  • 今回作るアプリケーション
  • /
  • #02
今回作るRailsアプリの全体像と機能の整理
1
  • 開発環境を整える
  • /
  • #01
macにRailsをゼロからインストールする
2
  • 開発環境を整える
  • /
  • #02
Rails apiモードのプロジェクトを作成し、Gitにコミットする
3
  • 開発環境を整える
  • /
  • #03
Bitbucketに公開鍵を追加し、Railsプロジェクトをpushする
4
  • 開発環境を整える
  • /
  • #04
HerokuCLIのインストールとherokuアプリケーションの作成
1
  • RailsをHerokuにデプロイする
  • /
  • #01
Herokuのデータベース設定と開発に便利なgemを導入する
2
  • RailsをHerokuにデプロイする
  • /
  • #02
HerokuにPumaを導入するためのRailsセットアップ
3
  • RailsをHerokuにデプロイする
  • /
  • #03
Railsに"Hello"を表示してHerokuへデプロイする
1
  • RailsとNuxt.jsを共存させる
  • /
  • #01
【RailsとNuxt.jsの共存】Rails上にNuxt.jsのプロジェクトを構築しよう
2
  • RailsとNuxt.jsを共存させる
  • /
  • #02
Nuxt.jsからRailsへ、初めてのapi通信でHelloを表示しよう
3
  • RailsとNuxt.jsを共存させる
  • /
  • #03
Nuxt.jsにVuetify2.0を導入してFont Awesomeもインストールするぜ
4
  • RailsとNuxt.jsを共存させる
  • /
  • #04
初めてのRailsApiアプリの公開。Herokuにデプロイする準備と実際のデプロイまで
1
  • データベース設計
  • /
  • #01
データベースを正規化する前に、会計システムの勘定科目データを整理する
2
  • データベース設計
  • /
  • #02
会計システムのデータベース設計に挑む
1
  • バージョンアップ情報
  • /
  • #01
【ご報告】Nuxt.jsを2.10.2にバージョンアップしました
2
  • バージョンアップ情報
  • /
  • #02
【ご報告】Railsを6.0.0にバージョンアップしました
1
  • Userモデル開発
  • /
  • #01
本番環境と開発環境でRailsのSeedデータを切り替える
2
  • Userモデル開発
  • /
  • #02
Railsにユーザーテーブルを作成する【テーブル確認コマンド】
3
  • Userモデル開発
  • /
  • #03
【Rails】EachValidatorクラスを使ったEmailカスタムバリデーション【lib以下読み込み】
4
  • Userモデル開発
  • /
  • #04
【Rails】エラーメッセージの日本語化【i18nとja.ymlのセッティング】
5
  • Userモデル開発
  • /
  • #05
【Rails】開発・テスト・本番環境の全てにユーザーSeedデータ投入する
6
  • Userモデル開発
  • /
  • #06
【Rails】ユーザーモデルのバリデーションをテストする
1
  • ログイン周りのレイアウト設計
  • /
  • #01
【Nuxt.js】ログインフラグでレイアウトを切り替えるテクニック【2019/12/07追記あり】
2
  • ログイン周りのレイアウト設計
  • /
  • #02
【Nuxt.js】ウェルカムページのレイアウト構築【sassの導入】
3
  • ログイン周りのレイアウト設計
  • /
  • #03
【Nuxt.js】ログイン周りの入力フォームコンポーネント設計【2019/12/07追記あり】
4
  • ログイン周りのレイアウト設計
  • /
  • #04
【Nuxt.js】会員登録フォームを構築してサインアップページを完成させる
独学プログラマ
独学でも、ここまでできるってよ。
CONTACT
Nuxt.js制作のご依頼は下記メールアドレスまでお送りください。