Help us understand the problem. What is going on with this article?

FactoryBot + Bullet + RSpec

N+1問題 を検出する bullet という便利なgemがありますが、
ドキュメントに書かれたように Bullet.raise = true と指定してもうまく動いてくれません。

もとのコードはこんな感じです

# spec/rails_helper
RSpec.configure do |config|
  config.before do
    Bullet.start_request
  end

  config.after do
    Bullet.end_request
  end
end

# spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  let(:user) { create :user }
  before { create_list :article, 100, user: user }

  it { user.articles.map(&:title) }
end

例外でテストが落ちてくれるはずですが落ちてくれません。

原因

コードを読んでみたところ、 bulletでは BULLET_DEBUG=true を渡してコードを実行するとデバッグ出力されることがわかりました。
実行してみたところ、

[Bullet][Detector::NPlusOneQuery#add_impossible_object] object: Article:1

という出力が大量に出て、 create_list で作られたレコードがすべてホワイトリストのようなものにぶちこまれていることがわかりました。
どうやらレコードを作ったあとに Bullet.start_request を呼ばないといけないらしいのですが、
レコードを作る before は基本的に it が書かれる場所で、(rails_helperを含む)親contextの before は必ずそれより先に実行されてしまいます。

before を素直に使うことは諦めてexample実行のタイミングを乗っ取ることにしました

乗っ取りポイント

exampleの実行は、 RSpec::Core::Example#run というメソッドの中で行われます。
実際のテストの実行(it の中身の実行)は instance_exec なのでちょっと乗っ取りにくそうです。
そこで run_before_examplerun_after_example をいじってみることにしました。

# spec/support/run_with_bullet.rb

module RunWithBullet
  def run_before_example
    super
    Bullet.start_request
  end

  def run_after_example
    Bullet.end_request
    super
  end
end

RSpec::Core::Example.prepend(RunWithBullet)

この状態で BULLET_DEBUG=true をつけて実行すると、

[Bullet][detect n + 1 query]

という表示が大量に出力され、N+1問題の検出が成功していることがわかります。
しかしまだ例外は発生しません。

例外を出させる

大変世界観が難しいのですが、どうやら Bullet.raise = true で設定できる例外出力モードは、通知の一種 らしいです。
同作者が作った uniform_notifier というgemの中に通知の方法の一つとして用意されています。
Bullet::Rack を読んでいてやっと気づきました。
Bullet::Rack と同じように、 Bullet.perform_out_of_channel_notifications を実行してやりましょう。

  def run_after_example
    Bullet.perform_out_of_channel_notifications if Bullet.notification?
    Bullet.end_request
    super
  end

Bullet.notification? は通知すべきものがあるか?(引っかかったものがあるか?) を調べるメソッドです。
正気か?

ともあれ、これで例外が発生してテストが落ちるようになります。

あとがき

before と違い after はそれほど順番にシビアではないので、 after は普通に書いても良いかもしれません。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away