savanna.io の日報からの転載。ちゃんと整形したり説明詳しく入れてメインのブログで公開しようと思ってたんだけど時間がなさすぎた
- palkan/n_plus_one_control: RSpec and Minitest matchers to prevent N+1 queries problem
- N+1をテストで検出できて便利
- 実際に同じテストを、DBレコード数だけ変えて複数回実行して発行したクエリ数を比較する
- クエリ数が違うとN+1では?となりテストを失敗させることができる
- N+1が起きるのは実際のユーザがアクセスしたときなので、システムスペックでは、と思いそうしていた
- しかしsystem specだと、ajaxを利用しているページを表示したときにタイミング次第でajaxリクエスト処理時のSQLを計算に入れてしまいテストがコケる
- 具体的にはturbo-frameで遅延ロードを追加ところこれが発覚したけど、turbo-frameに限らずajax使っているページでは起こり得る
- 他になんかいい方法あるかな、と考えたがN+1をチェックするところはrequire specを使うのがいいんじゃないか、という結論に達した
request specでログイン後のリクエストを投げる
- ↑ということでrequest specを久しぶりにちゃんと書こうと思ったらログインどうやるんだっけ?となったのでまとめた
1. allow_any_instance_of
- 手っ取り早いがハック感ある
before do allow_any_instance_of(ApplicationController).to receive(:current_user).and_return(ログインさせたいユーザオブジェクト) end
2. session
get root_path
session[:user_id] = user.id
get after_login_path
- これはうまく動かなかった
- 最初にroot_pathにアクセスしているのは、
session
メソッドが@request
が存在している前提で書かれており、最低一度アクセスしないといけないため - 昔はこれで実装していたような記憶がうっすらあるが、Rails5からはできなくなったのかも
- rails5 can't access session in ActionDispatch::IntegrationTest · Issue #23386 · rails/rails
- ↑にある再現コードでsessionが入らないことを確認したのでもうできない、という認識で良さそう
3. ログイン用のリクエストを事前に投げる
- request specもE2Eの一種と考えると、ちゃんとログイン用のリクエストを投げるのがお行儀が良いのでは、という気がする
- dhhも推奨している方式
- これはOmniAuthでtwitterログインしている前提
OmniAuth.config.test_mode = true OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new( provider: 'twitter', uid: idをいれる, info: { nickname: ユーザ名をいれる } ) post '/auth/twitter' follow_redirect! # OmniAuthはリダイレクト後にログインセッションを生成しているのでこれがないとログインできない