配列内に存在? shared_example: 小林研 Rails Tips (14)

はじめに

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

#  describe "一つの <%= class_name %> オブジェクトについて" do
#    subject { <%= singular_table_name %> }

#    it_behaves_like "配列内に存在?", %w[
#      described_class.foo\ bar
#    ]
#    it_behaves_like "配列内に存在しない?", %w[
#      described_class.foo\ bar
#    ]
#    it_behaves_like "同じ?", %w[
#      described_class.foo\ bar
#    ]
#    it_behaves_like "一致しない?", %w[
#      described_class.foo\ bar
#    ]
#  end

配列内に存在?

今日は「配列内に存在?」「配列内に存在しない?」になります。これらは where 条件の境界値チェックに使っています。 今回のサンプルは以前にも出てきた年度 (year) です。pre_years(year) は指定された year よりも以前のものを取り出す scope です。year の年は含みます。pre_years_without_year(year) も同様ですが year の年は含みません。最後の year_value_is(v) は年度が のものだけに一致します。これらの境界値チェックをまとめてできるように、文字列の配列で scope を指示します。文字列なのはこれらが it の外側であるために fixtures や subject などが利用できないためです。また、%w を使っているため、区切り以外のスペースは \ を使ってエスケープしています。

describe "一つの #{described_class} オブジェクトについて" do
    subject { years :y2019 }
    it_behaves_like "配列内に存在?", %w[
      described_class.pre_years\ subject
      described_class.pre_years_without_year\ years(:y2020)
      described_class.year_value_is\ 2019
    ]

    it_behaves_like "配列内に存在しない?", %w[
      described_class.pre_years\ years(:y2018)
      described_class.pre_years_without_year\ subject
      described_class.year_value_is\ 2018
    ]

最初の「配列内に存在?」の shared_example は以下のようになります。文字列を eval で展開して得られた配列に subject が含まれるかどうかをチェックします。

shared_examples_for "配列内に存在?" do |*args|
  it "上記の配列に含まれること" do
    args.flatten.each do |str|
      print "\t#{str}\n" if documentation_formatter?
      expect(subject).to be_in? eval(str)
    end
  end
end

同じくの「配列内に存在しない?」の shared_example は以下のようになります。こちらも文字列を eval で展開して得られた配列に subject が含まれないことをチェックします。これら二つの shared_example によって境界値がチェックできるわけです。

shared_examples_for "配列内に存在しない?" do |*args|
  it "上記の配列に含まれないこと" do
    args.flatten.each do |str|
      print "\t#{str}\n" if documentation_formatter?
      expect(subject).not_to be_in? eval(str)
    end
  end
end

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

条件判定なのでこれらは where を使った scope になります。year 属性の値を使って判定をしているだけです。

  scope :pre_years, ->(y) { where arel_table[:year].lteq y.year }
  scope :pre_years_without_year, ->(y) { where arel_table[:year].lt y.year }
  scope :year_value_is, ->(y) { where arel_table[:year].eq y }

おわりに

今回は where を使った条件判断のテストを簡潔に書くための shared_example を紹介しました。基本的には、where の scope を書いた場合にはこれらを使ってテストを書くようにしています。