HotwireなRailsアプリケーションのRequest spec事情
厳密には弊プロダクトはHotwireではないのでタイトルは釣りかもしれません.
しかし、RailsでHTMLをレンダリングしているため、HTMLをRailsでレンダリングする場合のRSpec事情については同じことが言えるので色々書いていこうと思います。
この記事については、VueからRailsへの置き換えを担当してくれたメンバーから色々テスト方針について質問をもらったこともあり、その質問に対しての回答を改めてできればと考えております。
Railsの置き換えを進めている記事については下記をご覧ください。
FeatureSpec及びSystemSpecについては、E2EテストサービスであるAutifyを利用していることもあって現在は利用をあまりしていない状態ですので、この記事での言及はしません。弊社でのAutifyの活用事例は下記記事をご覧ください。
Request specでのテスト内容
RailsでHTMLをレンダリングしているということは、Request specでほぼ完全なHTMLが手に入るということです。
よって、JSを動かさない部分はHTMLだけでもテストを行うことが可能です。
簡単に文字列をチェックするテストも、NokogiriをはじめとしたHTMLパーサーを使えば細かく画面をテストすることも可能だと思います。
当然e2eほど実行環境は近くはありませんが、ちょうどユニットテストとE2Eテストの間で、よく見るテストトロフィーの図の「Integration Test」に近いことをできるのではないかと思っています。[1]
JSを含んだコードのテストはできないため、Request specでのテスト範囲を広げるためには複雑なビジネスロジックだったりを可能な限りフロントエンドではなくRailsなどのバックエンドに寄せていく必要があります。
Request specでHTMLをテストするのはすこしつらい
Request specで幾分かの画面に関わるテストをある程度カバーできるのはRequest specはE2Eよりも安定していることもあってRailsでHTMLをレンダリングしているHotwireの強みであると考えています。
しかし、Request specでHTMLをパースして検証するのは当たり前ですがJSONよりつらいのが難点で、レンダリングされている順番だったり正しいか検証するのは面倒です。
なので、簡単にHTMLの検証を行うカスタムマッチャーを準備しておくのがお勧めです。
Capbara::DSLをRequest specでincludeする
GitHubが提供しているViewComponentsではCapybara::DSLをincludeして、カスタムマッチャーを使えるようにしているようです。
同じようにCapybara::DSL
をRequest specでも利用することで同じカスタムマッチャーを利用できるようになります。
config.include Capybara::RSpecMatchers, type: :request
上記をrails_helper.rbに記載すると、
have_selector
のCapybara::DSL
のマッチャーをRequest Spec上で利用することができます。
it "displays a list of posts" do
expect(response.body).to have_selector('.post', count: 4)
end
他にも have_text
などのCSSセレクターを利用したSpecを実装できます。
SpecHelperをNokogiriでつくる
Capybaraに依存したくない場合は、SpecHelperをNokogiriで作る選択も取れます。
RSpec::Matchers.define :nokogiri_selector do |selector, count: nil|
match do |response_body|
doc = Nokogiri::HTML(response_body)
elements = doc.css(selector)
# 件数の検証
if count
return false unless elements.size == count
end
true
end
failure_message do |response_body|
message = "expected to find #{count || 'any'} elements matching '#{selector}'"
if count
message += ", but found #{Nokogiri::HTML(response_body).css(selector).size}"
end
message
end
end
it "displays a list of posts" do
expect(response.body).to nokogiri_selector('.post', count: 4)
end
このようにHTMLをパースするマッチャーを自作することで、HTMLの検証を行うことがちょっとだけ簡単になります。
時には割り切りも必要
Rails上でデータがレンダリングされている順番をテストしたい場合はおそらくHTMLに対して何かしらのセレクターが必要になるケースがあると思います。
このあたりの議論はフロントエンドのテストでも一定議論されている話題ではあるので、Request specでのHTML検証においても割り切ってしまい、テスト用のHTML属性をつけてしまうのが今はいいかなと思っています。
ViewSpecやHelperSpecなど、Request spec以外のテストで担保する
Request specで細かくテストを書くのではなく、HTMLをレンダリングする際に利用しているHelperやViewに対して網羅的にSpecを書くことで動作を担保するケースも取れると思います。
確かにViewやHelperが複雑なのにSpecが書かれていない現場もそれなりにはあるので、Request specがつらい場合、ViewSpecやHelperSpecを書いて担保してもよいのかと思いました。[2]
よりユニットテストに近い層のテストではあるので、テストの運用方針だったりはチーム内で一定議論が必要かと思います。
ViewについてのSpecだったりは下記の記事を参考にするのが良いかと思います。
JSのテストはどうする?
もちろんJSなしではWebアプリケーション開発を行うのは到底不可能なので、ここは素直にJestを使ったりでテストをするようにしています。
重要なビジネスロジックを使ったりはできるだけフロントエンドでは行わないようにして、どうしてもというシーンはフロントエンドライブラリに依存しない純粋関数で実装を行ってJestのテストを書くようにしてテストを行いやすくしています。
まとめ
昨今ではRailsがAPIに徹するプロダクトが多いのか、Hotwireなプロダクトでどのようにテストを運用しているかの情報がないので、Request specでの工夫だったりをまとめてみました。
もし参考になったりした部分やこんなことやっているよ!みたいな知見も、コメントやXでぜひ教えてください!
Discussion