Rails 6で認証認可入り掲示板APIを構築する #11 userモデルのテストとバリデーション追加

←[Rails 6で認証認可入り掲示板APIを構築する #10 devise_token_auth導入](https://qiita.com/rf_p/items/43f1f12c473e1b02e766)


# テストの準備

userにバリデーションとテストを実装します。
テストファーストではなくなってしまっていますが、deviseは特殊なので、ちゃんと動くところまで実装を済ませたかったため、順番が前後しています。

まずはテストに必要なファイルを準備します。
userモデルはdevise_token_authで生成されたため、rspecとfactoryBotのファイルがありません。
以下コマンドで生成します。

```powershell
$ rails g rspec:model user
      create  spec/models/user_spec.rb
      invoke  factory_bot
      create    spec/factories/users.rb
```

factoryを先に作ります。

```spec/factories/users.rb
# frozen_string_literal: true

FactoryBot.define do
  factory :user do
    provider { "email" }
    sequence(:email) { |n| "test#{n}@example.com" }
    uid { email }
    password { "password" }
    remember_created_at { nil }
    name { "MyString" }
    tokens { nil }
  end
end
```

userテーブルのカラムは何があったっけ？となったら、db/schema.rbを見ると出力結果が確認できます。

factoryファイルで注目すべきは3点。
`    sequence(:email) { |n| "test#{n}@example.com" }
    uid { email }
    password { "password" }`
この3つです。

`sequence(:email) { |n| "test#{n}@example.com" }`
この処理の`n`には連番が入ってきます。1レコード目はtest1@example.com, 2レコード目はtest2@example.com, …となります。

わざわざこうやっているのは、emailカラムがunique制約がかかっており、同一文字列だと登録できないためです。
例えばここを定数で`email { "test@example.com" }`とすると、`create_list(:user, 10)`で該当メールアドレスが2個以上になってコケちゃうわけですね。

`uid { email }`
ここはシンプルに、uidにemailの変数を入れているだけです。
deviseの挙動として、providerがemailの場合はuid=emailとなります。

`password { "password" }`
DBのカラム上はencrypted_passwordですが、そこにはハッシュ化された文字列が保存されます。
パスワード設定時はpasswordに値を入れる必要があるため、schema.rbとカラム名が一致しないのです。

# userモデルのテスト

postと似たテストを実行したいので、spec/models/post_spec.rbをコピー。
`Post`を`User`、`subject`を`name`、`body`を`email`に置換。

```rb:spec/models/user_spec.rb
# frozen_string_literal: true

require "rails_helper"

RSpec.describe User, type: :model do
  describe "name" do
    context "blankの時に" do
      let(:user) do
        build(:user, name: "")
      end
      it "invalidになる" do
        expect(user).not_to be_valid
      end
    end
    context "maxlengthにより" do
      context "30文字の場合に" do
        let(:user) do
          build(:user, name: "あ" * 30)
        end
        it "validになる" do
          expect(user).to be_valid
        end
      end
      context "31文字の場合に" do
        let(:user) do
          build(:user, name: "あ" * 31)
        end
        it "invalidになる" do
          expect(user).not_to be_valid
        end
      end
    end
  end

  describe "email" do
    context "blankの時に" do
      let(:user) do
        build(:user, email: "")
      end
      it "invalidになる" do
        expect(user).not_to be_valid
      end
    end
    context "maxlengthにより" do
      context "100文字の場合に" do
        let(:user) do
          build(:user, email: "@example.com".rjust(100, "a"))
        end
        it "validになる" do
          expect(user).to be_valid
        end
      end
      context "101文字の場合に" do
        let(:user) do
          build(:user, email: "@example.com".rjust(101, "a"))
        end
        it "invalidになる" do
          expect(user).not_to be_valid
        end
      end
    end
    context "email形式により" do
      context "正しい文字列の場合に" do
        let(:user) do
          build(:user, email: "test@example.com")
        end
        it "validになる" do
          expect(user).to be_valid
        end
      end
      context "正しくない文字列の場合に" do
        let(:user) do
          build(:user, email: "test@example")
        end
        it "invalidになる" do
          expect(user).not_to be_valid
        end
      end
    end
  end
end
```

以下箇所は、postからのコピーではなく独自に置き換えてます。

```rb
build(:user, email: "@example.com".rjust(100, "a"))
build(:user, email: "@example.com".rjust(101, "a"))
```

rjustは文字列が右詰めになるように指定文字で埋めるメソッドです。
`rails c`で実行してみます。

```powershell
$ rails c
[1] pry(main)> "@example.com".rjust(100, "a")
=> "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@example.com"
[2] pry(main)> "@example.com".rjust(100, "a").length
=> 100
```

こうすることによって、100文字ピッタリのダミーデータを生成できます。

# userモデルのバリデーション実装

```diff:app/models/user.rb
 # frozen_string_literal: true

 #
 # ユーザークラス
 #
 class User < ActiveRecord::Base
   # Include default devise modules. Others available are:
   # :confirmable, :lockable, :timeoutable and :omniauthable
   devise :database_authenticatable, :registerable,
          :rememberable, :validatable
   include DeviseTokenAuth::Concerns::User


+  validates :name, presence: true, length: { maximum: 30 }
+  validates :email, presence: true, length: { maximum: 100 }
end
```

これでrspecのテストを通過するようになります。

あれ？メールアドレスのフォーマットバリデーション書いてないけどなんでOKなの？と感じたら鋭いです。
実はdeviseの`:validatable`でemailのフォーマットチェックは行われているので、email形式確認バリデーションはdevise依存モデルの場合特に指定不要です。

# authのrequest specを書く

今回のチュートリアルの中では、登録とログインだけの簡易的なテストを書いて終わりにします。

```powershell
$ rails g rspec:request v1/auth
      create  spec/requests/v1/auths_spec.rb
$ mv spec/requests/v1/auths_spec.rb spec/requests/v1/auth_spec.rb
```

authsが気持ち悪いのでauthにrenameしておきます。
基本的にはspec/requests/v1/posts_spec.rbを参考にしながら、auth_spec.rbを書いていきます。

```rb:spec/requests/v1/auth_spec.rb
# frozen_string_literal: true

require "rails_helper"

RSpec.describe "V1::Auth", type: :request do
  describe "POST /v1/auth#create" do
    let(:user) do
      attributes_for(:user, email: "signup@example.com", name: "signupテスト")
    end
    it "正常レスポンスコードが返ってくる" do
      post v1_user_registration_url, params: user
      expect(response.status).to eq 200
    end
    it "1件増えて返ってくる" do
      expect do
        post v1_user_registration_url, params: user
      end.to change { User.count }.by(1)
    end
    it "nameが正しく返ってくる" do
      post v1_user_registration_url, params: user
      json = JSON.parse(response.body)
      expect(json["data"]["name"]).to eq("signupテスト")
    end
    it "不正パラメータの時にerrorsが返ってくる" do
      post v1_user_registration_url, params: {}
      json = JSON.parse(response.body)
      expect(json.key?("errors")).to be true
    end
  end

  describe "POST /v1/auth/sign_in#create" do
    let(:user) do
      create(:user, email: "signin@example.com", name: "signinテスト")
      { email: "signin@example.com", password: "password" }
    end
    it "正常レスポンスコードが返ってくる" do
      post v1_user_session_url, params: user, as: :json
      expect(response.status).to eq 200
    end
    it "nameが正しく返ってくる" do
      post v1_user_session_url, params: user
      json = JSON.parse(response.body)
      expect(json["data"]["name"]).to eq("signinテスト")
    end
    it "不正パラメータの時にerrorsが返ってくる" do
      post v1_user_session_url, params: {}
      json = JSON.parse(response.body)
      expect(json.key?("errors")).to be true
    end
  end
end
```

ここまで書いて、rubocopとrspecが正常に通ればcommitします。


# 続き

→[Rails 6で認証認可入り掲示板APIを構築する #12 userとpostの関連付け](https://qiita.com/rf_p/items/bcdcbb7c6cbd725b5b94)
【[連載目次へ](https://qiita.com/rf_p/items/a846553c11571f4177eb#%E7%9B%AE%E6%AC%A1)】