n_plus_one_controlはrequest specで使うのが良いという話とrequest specでログイン後のリクエストを投げるための方法について

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からはできなくなったのかも

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はリダイレクト後にログインセッションを生成しているのでこれがないとログインできない