降順確認 shared_example: 小林研 Rails Tips (11)

はじめに

Rails Tips の 11 回目です。連日の model のテストを簡単に書くための shared_example の紹介の続きです。しばらくは私の Shared example の雛形 (lib/templates/rspec/model/model_spec.rb)を一つずつ解説していく形です。

#  describe "<%= class_name %> クラスについて" do
#    context "order_sort_order" do
#      subject { described_class.order_sort_order }
#      it_behaves_like "昇順確認", 0, ->(o) { o.sort_order }
#    end

#    context "order_bar_desc" do
#      subject { described_class.order_bar_desc }
#      it_behaves_like "降順確認", 99999, ->(o) { o.bar }
#    end
#
#    context "Class methods" do
#      subject { described_class }
#
#      it_behaves_like "単一メソッド呼び出し" do
#        let(:test_set) do
#          {
#              foo: [nil, [true, false, true]],
#          }
#        end
#      end
#    end

#    describe "get_or_create method" do
#      subject { -> { described_class.get_or_create(*params) }}
#      context '既存のものを取得' do
#        let(:params) { [object.xxx, object.yyy] }
#        it_behaves_like "オブジェクト数が変化しない?", <%= class_name %>
#      end
#
#      context '新規作成' do
#        let(:params) { [object.xxx, other.yyy] }
#        it_behaves_like "オブジェクトが1増えるか?", <%= class_name %>
#      end
#    end
#  end

降順確認

今日は「降順確認」になります。昨日に引き続きデータの並び替えに関するテストになります。年度 (year) は最近の年度が一番利用されることが多いので、降順で並び替えることのほうが多いです。昨日の昇順確認とは逆でどの値よりも大きい値を初期値に設定します。

    context "order_year_desc" do
      subject { described_class.order_year_desc }
      it_behaves_like "降順確認", 2100, ->(y) { y.year }
    end

また、行事を示す event モデルも逆順に並べることが多いです。fixtures のテストデータは基本的に過去のものを用意しているので、今日を初期値に設定しています。

    context "order_event_date_desc" do
      subject { described_class.order_event_date_desc }
      it_behaves_like "降順確認", Date.today, ->(o) { o.event_date }
    end

この shared_example は以下のようになります。昨日のものの不等号が反対になっているだけです。取得した Array に対して、Proc を呼び出し start で指定されたものと比較します。比較結果が start よりも小さければテストが通過します。評価が終わると現在の値を start に置き換えるので、すべての要素に対して常に前の値より小さい単調減少の関係にあることがテストできることになります。

shared_examples_for "降順確認" do |start, block|
  it "降順に並んでいること" do
    subject.each do |object|
      value = block.call(object)
      expect(value <=> start).to be <= 0
      start = value
    end
  end
end

正しく動作させるための実装

昨日と同様に、このテストが正しく通過するためには、モデルに対して scope を設定すればよいことになります。year モデルにおける order_year_desc は以下のようになります。逆順なので arel_table に desc を追加しています。

scope :order_year_desc, -> { order arel_table[:year].desc }

event モデルにおける order_event_date_desc も同様です。

scope :order_event_date_desc, -> { order arel_table[:event_date].desc }

おわりに

今日のネタは昨日とほとんど変わらない手抜きなものになりました。逆順なので考えられる最大値を設定する必要があるので、注意が必要ですね。