リア充爆発日記

You don't even know what ria-ju really is.

Rails4のconcernsなmoduleのテストをrspecで書く方法

Railsとcapybaraで何かをドラッグアンドドロップしてソートするテストを書くではカテゴリをソートするfeaturesなテストを書いたけど、アレには当然modelのテストもある。

モデルにはCategory.reorderという並び替えを行うメソッドがあって、これのテストはcategory_specに書いていた。
が、別の種類のカテゴリが必要になり、もともとあったCategoryと同じ仕様でソートができる仕様であったため、FooCategoryとBarCategoryに分け、このソート処理をconcernsの下にCategorySortable moduleとして切り出した。

app/models/concerns/category_sortable.rb

module CategorySortable
  extend ActiveSupport::Concern
  included do
    acts_as_nested_set
  end
  module ClassMethods
    def reorder(ids)
      # some logic here....
    end
  end
end

このmoduleをテストするのはどうしたものか、と調べていたところ、rspecにはそれを意図した仕組みが用意されていた。
https://www.relishapp.com/rspec/rspec-core/v/2-13/docs/example-groups/shared-examples!

具体的な手順としては、まず肝になるmodule部分のテストを以下のように書く。
spec/support/models/concerns/category_sortable_spec.rb

require 'spec_helper'

shared_examples "category sortable" do
  describe ".reorder" do
    before do
      described_class.reorder([category3.id, category1.id, category2.id])
    end

    it { expect(described_class.roots).to eq [category3, category1, category2] }
  end
end

supportの下に配置しているのは、ここだとspec実行時にロードされるからで、個別にrequireしたり、別途ロードするpathを設定したりするんだったらどこでもいいと思う。ただ、個別にrequireした場合、1度のテストで複数回呼ばれるような実行の仕方をすると以下のようなwarningがでてウザいのでおすすめしない。
追記)_specをつけても2回読み込まれてwarningがでる。

WARNING: Shared example group 'category sortable' has been previously defined at:
~snip~

で、このmoduleをincludeするmodelのテストはこんなかんじになる。
spec/models/foo_category_spec.rb

require 'spec_helper'
describe FooCategory do
  ~ some class specific tests ~
  it_behaves_like "category sortable" do
    let(:category1) { FactoryGirl.create(:foo_category) }
    let(:category2) { FactoryGirl.create(:foo_category) }
    let(:category3) { FactoryGirl.create(:foo_category) }
  end
end

spec/models/bar_category_spec.rb

require 'spec_helper'
describe BarCategory do
  ~ some class specific tests ~
  it_behaves_like "category sortable" do
    let(:category1) { FactoryGirl.create(:bar_category) }
    let(:category2) { FactoryGirl.create(:bar_category) }
    let(:category3) { FactoryGirl.create(:bar_category) }
  end
end

ということで、moduleのテスト側でshared_examplesで定義したものをit_behaves_likeで呼び出すかっこうになる。ここでdescribeしてるクラスがさっきのmoduleのテストのdescribed_classに該当する。テストの呼び出し方はもうちょっとバリエーションがあるので、この書き方がフィットしないケースは上記のRspecドキュメントを熟知すべし

なお、コピペで同じテストを書くものはリア充が地獄の火の中に投げ込むものであり、腹を切って死ぬべきである

以上、よろしくお願いいたします。

スーパーマリオ 3Dワールド

スーパーマリオ 3Dワールド