RSpecでsubjectを使用して配列を検査時、配列の長さを取得する

RSpecでsubjectを使用して、配列を

subject { user.errors[:name] }  # user.errors[:name] は配列

このように検査時、配列の長さを取得する方法が分からなかったので調べました。

結論

rspec-itsitsを使用する。

its(:size) { is_expected.to eq 3 }

のように書くとsubjectの検査対象の配列の長さを取得できます。

経緯

モデルのテストを書きました。

modelのコード↓

class User < ApplicationRecord
  validates :nickname, presence: true, length: { maximum: 32 },
                       format: { with: /\A[-0-9a-zA-Z#$%&()._]*\z/,
                                 message: 'には半角英数字と#$%&()-._のみ使用できます。' }
end

テストは以下のようになります。(全て書くと長いので一部のみ抜粋)

require 'rails_helper'

RSpec.describe User, type: :model do
  describe '#nickname' do
    context '空白のとき'do
      it 'エラーが出ること' do
        user = User.new(nickname: '')
        user.valid?
        expect(user.errors[:nickname]).to include("can't be blank")
      end
    end

    context '許可される文字の場合' do
      context '英大文字小文字数字#$%&()-._' do
        it 'エラーが出ないこと' do
          user = User.new(nickname: 'ARZatz809(_#-$%.&)')
          user.valid?
          expect(user.errors[:nickname]).to be_blank
        end
      end
    end

    context '許可されない文字の場合' do
      context '許可されていない文字"?"が含まれている場合' do
        it 'エラーが出ること' do
          user = User.new(nickname: 'correct?')
          user.valid?
          expect(user.errors[:nickname]).to include('には半角英数字と#$%&()-._のみ使用できます。')
        end
      end
    end
  end
end

これを綺麗にすると以下のようになります。(参考: 使えるRSpec入門・その1「RSpecの基本的な構文や便利な機能を理解する」 - Qiita )

require 'rails_helper'

RSpec.describe User, type: :model do
  describe '#nickname' do
    subject { user.errors[:nickname] }

    let(:user) { User.new(params) }
    let(:params) { {nickname: nickname} }

    before do
      user.valid?
    end

    context '空白のとき'do
      let(:nickname) { '' }
      it { is_expected.to include("can't be blank") }
    end

    context '許可される文字の場合' do
      context '英大文字小文字数字#$%&()-._' do
        let(:nickname) { 'ARZatz809(_#-$%.&)' }
        it { is_expected.to be_blank }
      end
    end

    context '許可されない文字の場合' do
      context '許可されていない文字"?"が含まれている場合' do
        let(:nickname) { 'correct?' }
        it { is_expected.to include('には半角英数字と#$%&()-._のみ使用できます。') }
      end
    end
  end
end

繰り返しがなくなってすっきりしました。

ここでふと思いました。 このテストコード、対象のエラーが含まれることはチェックできるけど、想定外のエラーが(間違って)含まれることはテストできていないじゃん、と。

ここでの方針としては、

  • includeでなく配列同士の比較を行う
  • エラーの数をチェックする

などが思いつきます。今回は

  • エラーの数をチェックする

方法にします。この方法をとることで、

  • エラーの順番は問わず
  • 想定外のエラーが間違って入っていることを防ぐ

ことができます。

さて、テストの対象はsubjectに書かれています。これのサイズを取得したい。 これにはitsを使用します。

RSpec3からitsはgemに分離されているのでインストールします。

Gemfile

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  gem 'rspec-rails'
  gem 'rspec-its'  # この行を追加
end

bundle installします。

テストを追加します。

its(:size) { is_expected.to eq 1 }のように書くとuser.errors[:nickname].sizeが1かどうかをテストすることができます。

RSpec.describe User, type: :model do
  describe '#nickname' do
    subject {user.errors[:nickname]}

    # 略

    context '空白のとき'do
      let(:nickname) { '' }
      it { is_expected.to include("can't be blank") }
      its(:size) { is_expected.to eq 1 }  # 追加
    end

    context '許可される文字の場合' do
      context '英大文字小文字数字#$%&()-._' do
        let(:nickname) {'ARZatz809(_#-$%.&)'}
        it { is_expected.to be_blank}
      end
    end

    context '許可されない文字の場合' do
      context '許可されていない文字"?"が含まれている場合' do
        let(:nickname) {'correct?'}
        it { is_expected.to include('には半角英数字と#$%&()-._のみ使用できます。')}
        its(:size) { is_expected.to eq 1 }  # 追加
      end
    end
  end
end

以上でsubjectに指定した配列のサイズ(長さ)をテストすることができました。

環境

参考