Rspec+Railsを使ったconcernの単体テストの書き方

Railsの開発にてmodelやcontrollerの共通処理をconcernとして切り出すことはよくあるパターンです。
modelやcontrollerはクラス(class)として表現するものの、concernはActiveSupport::Concernextendしたモジュール(module)として定義する必要があります。

require "active_support/concern"

module SampleConcern
  extend ActiveSupport::Concern

  included do
    def do_something
      :
    end
  end
end

ActiveSupport::Concern

便利な一方で、単体テストが書きにくい・責務が分散しやすいというデメリットがあります。
よくあるパターンとしてはconcernとして定義したモジュール(SampleConcern)をincludeしたmodelやcontrollerに対応するテストファイル中にconcern内で定義した関数の単体テストを書いてしまうということがあります。

models/user.rb

class User < ActiveRecord::Base
  include SampleConcern # concernに定義したSampleモジュールをinclude
  :
end

spec/models/user_spec.rb

RSpec.describe Activity, type: :model do
  context 'do_something' do # do_somethingはSampleConcernに定義された関数...
    :
  end
end

concernに定義した関数であればconcernに対応するテストファイルに単体テストを書くべきでしょう。
しかし、concernはモジュールとして定義されているため、スコープの扱いや関数の呼び出しが面倒なため、先ほども書いたように「単体テストが書きにくい」という問題があります。

今回はRailsのconcernに対するテストファイルで単体テストを書く方法を紹介したいと思います。

使用するconcernについて

クラスメソッドとインスタンスメソッドをそれぞれ1つ持つシンプルなconcernです。
面白い処理が思いつかなかったので、それぞれ文字列を返すだけの関数となっています。

app/models/concerns/sample_concern.rb

module SampleConcern
  extend ActiveSupport::Concern

  included do
    def self.class_method
      'class method!'
    end

    def instnace_method
      'instance method!'
    end
  end
end

テストファイル(spec)

本命のテストファイルです。
今回の肝はconcernをincludeしたクラスをいい感じに用意するという点にあります。最近Class.newという書き方を覚えたばかりなので、さっそく使ってみました。

spec/models/concern/sample_concern_spec.rb

require 'rails_helper'

RSpec.describe SampleConcern do
  let(:included_class) do
    Class.new do
      include SampleConcern
    end
  end

  context 'class_method' do
    it 'class method!が返ること' do
      res = included_class.class_method
      expect(res).to eq 'class method!'
    end
  end

  context 'instnace_method' do
    it 'instnace_method!が返ること' do
      res = included_class.new.instnace_method
      expect(res).to eq 'instance method!'
    end
  end
end

無名クラスがSampleConcernをincludeしているので、インスタンスメソッド・クラスメソッドの両方を呼び出すことができます。

let(:included_class) do
  Class.new do
    include SampleConcern
  end
end

www.okb-shelf.work