🏃

Railsのテストで値が変わらないときはreloadを疑おう

に公開

RSpecでテストを書いていて、「データが更新されているはずなのに、期待した値になっていない」と感じることがありました。

その原因のひとつが ActiveRecordのキャッシュ です。

ここでは、ActiveRecord + RSpecのテストで、モデルの更新が正しく検証できない問題と、reload メソッドによるその解決方法について解説します。


reload を使わないとテストが失敗することがある

次のような処理を持つコントローラがあるとします。

class PlansController < ApplicationController
  def destroy
    @plan = Plan.find(params[:id])
    @plan.soft_delete!
  end
end

これに対し、RSpecのテストを以下のように書いていました。

describe "#destroy" do
  let(:plan) { create(:plan) }

  it "削除日時が設定される" do
    delete :destroy, params: { id: plan.id }                             
    expect(plan.deleted_at).to be_present     
  end
end

このテストは、deleted_at が nil のままで失敗することがあります。しかし、実際にはデータベース上では更新されています。なぜテスト内では変更が反映されていないのでしょうか。

解決:reload を使ってインスタンスを最新状態にする

このような問題を防ぐには、reload を使ってインスタンスをデータベースの最新の状態に更新します。

it "削除日時が設定されること" do
  delete :destroy, params: { id: plan.id }                             
  expect(plan.reload.deleted_at).to be_present     
end

reload によって、キャッシュされていた情報が更新され、deleted_at の最新の値が正しく取得できます。

なぜ reload が必要なのか?

ActiveRecordのインスタンスは、一度作成されるとその状態をオブジェクト内にキャッシュします。言い換えるならば、一度DBから読み込んだ情報を、Rubyオブジェクトが自分の中に保持し続けるということです。
今回の場合↓

describe "#destroy" do
  let(:plan) { create(:plan) }

  it "削除日時が設定されること" do
    delete :destroy, params: { id: plan.id }                             
    expect(plan.deleted_at).to be_present  # 古い状態(nil)を見ている   
  end
end
  1. create(:plan) によって、Planのインスタンスが作成される

  2. この時点で、plan という オブジェクトは deleted_at: nil の状態を持っている
    → これは当然の初期状態。

  3. delete :destroy を呼ぶと、コントローラ経由でDB上のレコードの deleted_at が現在時刻に更新される
    → データベースの内容は正しく更新されています。

  4. しかし、plan オブジェクトは自分の中にある deleted_at: nil の情報を保持し続ける
    → ここがポイント!Rubyのオブジェクトは、更新されたDBの状態を自動では反映しないため、plan の属性は古いままです。これが ActiveRecordのキャッシュ です。

  5. その結果、expect(plan.deleted_at) はキャッシュされた古い状態(nil)を見てしまい、テストが失敗する

そのため、DB上で変更があっても、Rubyオブジェクトの属性はすぐには更新されないのです。

こちらの記事の「ActiveRecord Relationのプロパティとその役割」にも関連する内容が書かれているので、気になる方はぜひ参考にしてみてください。

まとめ

RSpecでActiveRecordで属性変更をテストする際は、reload を使って最新の状態にすることが大事です。特に、更新直後に属性をチェックするテストでは、reload を使うことで正確に検証することが可能になります。

参考文献

https://railsdoc.com/page/model_reload

ラブグラフのエンジニアブログ

Discussion