LoginSignup
1
1

More than 3 years have passed since last update.

RSpec - 'users validation'のテスト

Last updated at Posted at 2020-09-21

RSpec - 'users validation'のテストコードをレビューしてもらった結果

@jnchito さんにレビューしていただきました!
よければ上のリンクからどうぞ!

経緯

初めまして。Qiitaの投稿、rails、rspecの初心者です。お手柔らかにお願いします。
railstutorial で学習しながら、app/models/user.rbの'model spec'を書いています。
イケてる綺麗なテストコードにしたいと思い、色々な方のrspecの書き方の記事を拝見させてもらいました。
specを自分なりに工夫しながら書いていて、この書き方「結構イケてるのでは?」と思う反面、
いざ記事を参考にしながら自分のコードを見返していると何か微妙に感じてきてしまい、
「ルールに従ったテストコードがなんなのか」と迷子になり、
なかなかcommitできず、地獄を彷徨っています。
もしよければ、テストコードのレビュー、指摘をしていただけると幸いです。

参考

テストコードは、主に jnchito さんの以下を参考にさせてもらいました。

上記を拝見させて頂いて自分なりに工夫して書いたコードになります。
以下は参考にしたわけではないですが、拝見して自分のコードがゴミに思えた記事。

ネストイメージ

  • describe: User(model)#create(action) のとき attribute: name は、
  • context:  name should be present名前が存在するべき。
  • it:     valid or invalid のパターンを検証。

テスト作成において、ネストのイメージ(?)ですが、
model > action > attribute > validates: presence:true > valid or invalid
のような感じでグループ化を意識してspecを書きました。
(もう一点イメージとして、記事の最下部の$ bundle exec rspecの実行結果が綺麗に見えるようにといった感じです。)

地獄から抜け出せない理由

  • describecontextitの正しい使い分け。
    工夫して他の人が読む場合を考慮してイケてるわかりやすく書いたつもりですが、
    RSpecを綺麗に書くための基本Rule を拝見して、 「これじゃダメだ。」となった。
    (そもそもルールがあるのでしょうか?ある程度のルールを自分の中で作って作成する認識でいるのでそれが問題かもしれません。)
  • プロから見た僕のテストコード
    書いたテストコードがグリーンで「問題ない」と思ってしまっているので、
    「俺ならこうする!」が知りたいです。
  • いちいち、「保存できることorできないこと」は、各テストケースで実施しなくても良いものなのか。
    基本的にはuser.valid?として、確実に対象のテストで落ちていることを保証するために、
    エラー(errors[:name])の中身をinclude("can't be blank")のように確認していますが、
    その後、user.save!として、、(!これは例外で落ちてしまう気がしてますが、、)
    以下を確認するべきなのかを迷っています。
    • validのケースは、保存できること
    • invalidのケースは、保存できないこと

以下が「保存できることorできないこと」を確認しているコードの抜粋です。

# nameが存在していない場合は、無効であること
it 'is invalid because the name value does not exists' do
  user.name = nil
  user.valid?
  expect(user.errors[:name]).to include("can't be blank")

  expect(user.save).to be_falsey # ...この1行で保存できないことを確認

end

テストコード

テストの内容は、よくあるusers.name, user.emailに関するバリデーションです。
railstutorial いうところの こちら になります。

では、早速ですが、、

以下が現状、掛かっているバリデーション。

app/models/user.rb
class User < ApplicationRecord
  before_save :downcase_email

  validates :name,
            presence: true,
            length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email,
            presence: true,
            length: { maximum: 255 },
            format: { with: VALID_EMAIL_REGEX },
            uniqueness: { case_sensitive: false }

  private

    def downcase_email
      self.email.downcase!
    end
end

以下がFactoryBotで定義しているuser

spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { "hoge" }
    email { "foo@bar.com" }
  end
end

以下が実際のテストコード。

spec/models/user_spec.rb
require 'rails_helper'
require 'pry'

RSpec.describe User, type: :model do
  let(:user) { FactoryBot.build(:user) }

  describe '#create' do
    describe 'attribute: name' do
      context 'name should be present' do
        # nameが存在している場合は、有効であること
        it 'is valid because the name has the correct value' do
          expect(user).to be_valid
          expect(user.save).to be_truthy
        end
        # nameが存在していない場合は、無効であること
        it 'is invalid because the name value does not exists' do
          user.name = nil
          user.valid?
          expect(user.errors[:name]).to include("can't be blank")
          expect(user.save).to be_falsey
        end
      end

      context 'name should be length is 50 characters or less' do
        # nameが50文字以下の場合は、有効であること
        it 'is valid because the name length is 50 characters or less' do
          user.name = 'a' * 50
          user.valid?
          expect(user.errors[:name]).not_to include('is too long (maximum is 50 characters)')
          expect(user.save).to be_truthy
        end
        # nameが50文字を超える場合は、無効であること
        it 'is invalid because the name length is 50 characters over' do
          user.name = 'a' * 51
          user.valid?
          expect(user.errors[:name]).to include('is too long (maximum is 50 characters)')
          expect(user.save).to be_falsey
        end
      end

      context 'name should be not blank' do
        # nameが空白の場合は、無効であること
        it 'is invalid because the name is blank' do
          user.name = ' '
          user.valid?
          expect(user.errors[:name]).to include("can't be blank")
          expect(user.save).to be_falsey
        end
      end
    end

    context 'attribute: email' do
      context 'email should be present' do
        # emailが存在していない場合は、無効であること
        it 'is invalid because the email value does not exists' do
          user.email = nil
          user.valid?
          expect(user.errors[:email]).to include("can't be blank")
          expect(user.save).to be_falsey
        end
      end

      context 'email should be length is 50 characters or less' do
        # emailが255文字以下の場合は、有効であること
        it 'is valid because the email length is 255 characters or less' do
          user.email = 'a' * 243 + '@example.com'
          user.valid?
          expect(user.errors[:email]).not_to include('is too long (maximum is 255 characters)')
          expect(user.save).to be_truthy
        end
        # emailが255文字を超える場合は、無効であること
        it 'is invalid because the email length is 255 characters over' do
          user.email = 'a' * 244 + '@example.com'
          user.valid?
          expect(user.errors[:email]).to include('is too long (maximum is 255 characters)')
          expect(user.save).to be_falsey
        end
      end

      context 'email should be correct format' do
        # emailの形式が正しい場合は、有効であること
        it 'is valid because the email is the correct format' do
          user.email = 'user@example.com'
          expect(user).to be_valid
          expect(user.save).to be_truthy

          user.email = 'USER@foo.COM'
          expect(user).to be_valid
          expect(user.save).to be_truthy

          user.email = 'A_US-ER@foo.bar.org'
          expect(user).to be_valid
          expect(user.save).to be_truthy

          user.email = 'first.last@foo.jp'
          expect(user).to be_valid
          expect(user.save).to be_truthy

          user.email = 'alice+bob@baz.cn'
          expect(user).to be_valid
          expect(user.save).to be_truthy
        end
        # emailの形式が正しくない場合は、無効であること
        it 'is invalid because the email format is incorrect' do
          user.email = 'user@example,com'
          expect(user).not_to be_valid
          expect(user.save).to be_falsey

          user.email = 'user_at_foo.org'
          expect(user).not_to be_valid
          expect(user.save).to be_falsey

          user.email = 'user.name@example.'
          expect(user).not_to be_valid
          expect(user.save).to be_falsey

          user.email = 'foo@bar_baz.com'
          expect(user).not_to be_valid
          expect(user.save).to be_falsey

          user.email = 'foo@bar+baz.com'
          expect(user).not_to be_valid
          expect(user.save).to be_falsey
        end
      end

      context 'email should be unique' do
        # 同一のemailが既に登録されている場合は、無効であること
        it 'is Invalid because the same email already exists' do
          dup_user = user.dup
          dup_user.email = dup_user.email.upcase
          dup_user.save!
          user.valid?
          expect(user.errors[:email]).to include('has already been taken')
          expect(user.save).to be_falsey
        end
      end

      # emailが小文字で保存されていること
      context 'email should be saved in lowercase' do
        it 'is valid because the email saved in lowercase' do
          user.email = 'Foo@Example.Com'
          user.save!
          expect(user.reload.email).to eq 'foo@example.com'
        end
      end

      context 'email should be not blank' do
        # emailが空白の場合は、無効であること
        it 'is invalid because the email is blank' do
          user.email = ' '
          user.valid?
          expect(user.errors[:email]).to include("can't be blank")
          expect(user.save).to be_falsey
        end
      end
    end

  end
end

上記のテストは、$ bundle exec rspecを実行(グリーン)した出力は以下。
テストコードのdescribecontextitの使い分けは、以下の出力が見やすくなるようにを意識してネストした感じなります。

User
  #create
    attribute: name
      name should be present
        is valid because the name has the correct value
        is invalid because the name value does not exists
      name should be length is 50 characters or less
        is valid because the name length is 50 characters or less
        is invalid because the name length is 50 characters over
      name should be not blank
        is invalid because the name is blank
    attribute: email
      email should be present
        is invalid because the email value does not exists
      email should be length is 50 characters or less
        is valid because the email length is 255 characters or less
        is invalid because the email length is 255 characters over
      email should be correct format
        is valid because the email is the correct format
        is invalid because the email format is incorrect
      email should be unique
        is Invalid because the same email already exists
      email should be saved in lowercase
        is valid because the email saved in lowercase
      email should be not blank
        is invalid because the email is blank

どうぞ、宜しくお願いします。

1
1
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1