【rails】rspecでcontrollerのconcern単体テストをやる方法
ことの始まり
巨大なcontrollerを分割した際、共通で使うメソッドはconcernにまとめておこうと考え。
controllerの単体テストでわざわざconcernの内容も一緒にやるのは馬鹿らしいので、concernの単体テストをやりたくなったが、ちょっと困ったので色々調べた。
せっかくなので、忘れないようにまとめておこう。
ベースとなるconcern
今回作ったのはこんな感じのconcern。
module HogeConcern extend ActiveSupport::Concern def fuga(order) # ハッシュを返し、nameキーに'maccho_'をつける end def piyo piyo_params = params.permit(~~) # permitの中は省略 # 頭に'maccho_'をつけたstrを返す end end
fugaメソッド
は引数にorder
を持っている。
一方で、piyoメソッド
はpost時に渡されたparamsをそのまま使っている。
引数がある場合
まずは引数がある場合から。
とりあえずテストのベースとなる部分を作っておく。
require 'rails_helper' describe HogeConcern do context '.fuga' do let(:order) do FactoryBot.buid(:order, {name: hoge_name}) end subject { fuga(order) } context 'hoge_name' do let(:hoge_name) { 'yasagori' } it { is_expected.to eq({ name: 'maccho_yasagori' }) } end end end
当然、このままだと通らない。
まずは、struct
を使ってテスト用のクラスを作る。
クラスを作る際に、HogeConcern
を忘れずにincludeしておく。
そして、作成したクラスをnew
する。
context '.fuga' do let(:test_class) { Struct.new(:concern) { include HogeConcern } } let(:concern) { test_class.new } let(:order) do FactoryBot.buid(:order, {name: hoge_name}) end subject { concern.fuga(order) } context 'hoge_name' do let(:hoge_name) { 'yasagori' } it { is_expected.to eq({ name: 'maccho_yasagori' }) } end end
concern自体はcontrollerにincludeして使われているので、単純にメソッドを呼び出しても使えない、ということ。
なのでstruct
を使って擬似的にクラスを作ってあげてからincludeすることでメソッドを呼び出せるようになる。
呼び出すときはsubject
のような書きっぷりが必要(メソッドの呼び出しだから)
post parameterを使う場合
問題はこっち。
controllerがないのにどうやってpost parameterを作ればいいんだ…
結論を先に言うと、擬似的にcontrollerを作ってあげる必要があった。
describe HogeConcern, type: :controller do # typeをcontrollerにする context '.piyo' do controller(ApplicationController) do include HogeConcern def fake_action @piyo = piyo end end before { routes.draw { post 'fake_action' => 'anonymous#fake_action' } } subject { controller.instance_variable_get("@piyo") } context 'piyo_str' do before { post :fake_action, params: { name: 'yasagori' } } it { is_expected.to eq('maccho_yasagori') } end end end
まずcontrollerを設定する。
concernのテストをしたいので、includeを忘れずに。
その中で、呼び出すメソッドを定義しておく。
定義したメソッドはroutes.draw
でrouteの定義付をしてあげる。
subject
はメソッドの中で定義したインスタンス変数を呼び出すためcontroller.instance_variable_get
を使った。
これで、controllerテストの中でconcernのテストを行う必要がなくなる。
まとめ
結果的にconcernの単体テストでpost parameterを呼び出そうとすると、controllerの設定が必要になってしまった。
が、それぞれのcontrollerのなかでテストを行う必要がなくなったので、最終的なコードは短くまとめられ、すっきりさせられた。