オブジェクト数が…? shared_example: 小林研 Rails Tips (13)

はじめに

Rails Tips の 13 回目です。連日の 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

オブジェクト数が…?

今日は「オブジェクト数が変化しない?」「オブジェクトが1増えるか?」になります。何に使うかというと、get_or_create 系のメソッドに利用します。これは、条件に満足するオブジェクトがあればそれを取得し、なければ新規に作成するものです。二つの状態があるので、どちらになるかをテストするだけです。subject を Proc にすることで、実行前と実行後のオブジェクト数を比較する形になります。 今回のサンプルはキャンパスごとの年度情報を保持する campus_nendo_info です。これは、year_id と campus_id で複合一意制約となるため、既存のものがある場合にはそれを取得します。この場合は「オブジェクト数が変化しない」ことになります。一方、既存のものが存在しない場合には新規にオブジェクトが作成されるため、「オブジェクトが1増える」となります。

    describe "get_or_create method" do
      subject { -> { described_class.get_or_create(*params) } }
      context "既存のものを取得" do
        let(:params) { [object.year, object.campus] }
        it_behaves_like "オブジェクト数が変化しない?", described_class
      end

      context "新規作成" do
        let(:params) { [object.year, arakawa] }
        it_behaves_like "オブジェクトが1増えるか?", described_class
      end
    end

最初の「オブジェクト数が変化しない?」の shared_example は以下のようになります。内容は簡単で Proc を呼び出し、前後でオブジェクト数が変化しないことを確認するだけです。

shared_examples_for "オブジェクト数が変化しない?" do |klass|
  it "#{klass}.count が変化しないこと" do
    expect { subject.call }.not_to change(klass, :count)
  end
end

同じくの「オブジェクトが1増えるか?」の shared_example は以下のようになります。こちらも内容は簡単で Proc を呼び出し、前後でオブジェクト数が 1 だけ変化したことを確認するだけです。

shared_examples_for "オブジェクトが1増えるか?" do |klass|
  it "#{klass}.count が 1 つ増えること" do
    expect { subject.call }.to change(klass, :count).by 1
  end
end

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

get_or_create 系のメソッドは find_or_create_by で簡単に作成できます。初期化をあちこちで記述したくないため、大体どのモデルにも get_or_create 系のメソッドは用意することが多いです。そのため、テストの雛形にも用意をしているわけです。

  # @param [Year] y 年度
  # @param [Campus] c キャンパス
  # @return [CampusNendoInfo] 作成もしくは取得した y 年度 c キャンパスの CampusNendoInfo
  def self.get_or_create(y, c)
    y.campus_nendo_infos.find_or_create_by(campus_id: c.id) do |cni|
      cni.lock_textbook_flag = false
      cni.lock_time_table_flag = false
      cni.input_shiken_kibou_flag = false
      cni.kenshin_date = nil
      cni.yosan_period = false
    end
  end

おわりに

処理によってオブジェクト数が変化するかどうか確認する shared_example は他にも多数用意していますが、それらの応用例として get_or_create 系のメソッドのテストを紹介しました。他のものもおいおい紹介することになると思います。