←Rails 6で認証認可入り掲示板APIを構築する #10 devise_token_auth導入
テストの準備
userにバリデーションとテストを実装します。
テストファーストではなくなってしまっていますが、deviseは特殊なので、ちゃんと動くところまで実装を済ませたかったため、順番が前後しています。
まずはテストに必要なファイルを準備します。
userモデルはdevise_token_authで生成されたため、rspecとfactoryBotのファイルがありません。
以下コマンドで生成します。
$ rails g rspec:model user
create spec/models/user_spec.rb
invoke factory_bot
create spec/factories/users.rb
factoryを先に作ります。
# 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
に置換。
# 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からのコピーではなく独自に置き換えてます。
build(:user, email: "@example.com".rjust(100, "a"))
build(:user, email: "@example.com".rjust(101, "a"))
rjustは文字列が右詰めになるように指定文字で埋めるメソッドです。
rails c
で実行してみます。
$ 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モデルのバリデーション実装
# 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を書く
今回のチュートリアルの中では、登録とログインだけの簡易的なテストを書いて終わりにします。
$ 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を書いていきます。
# 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します。