Railsの開発にてmodelやcontrollerの共通処理をconcernとして切り出すことはよくあるパターンです。
modelやcontrollerはクラス(class)として表現するものの、concernはActiveSupport::Concern
をextend
したモジュール(module)として定義する必要があります。
require "active_support/concern" module SampleConcern extend ActiveSupport::Concern included do def do_something : end end end
便利な一方で、単体テストが書きにくい・責務が分散しやすいというデメリットがあります。
よくあるパターンとしては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