要約
モデルの更新処理のテストが失敗する場合は、changeメソッドをブロックで書くことで更新後の値と比較されるようになってうまくいくかも?
it { expect { subject }.to change { some_model.reload.changed, true } }
課題
このようなchangedというカラムを持つシンプルなモデルを考えます。
# changed :bool not null
class SomeModel
validates :changed, presence: true
def initialize
self.changed = false
end
def change
self.changed = true
# このメソッド内でレコード保存する何らかの処理
end
end
このモデルのテストを以下のように書いて
「あれ、changedの値を更新したはずなのに、されてなくてテストが落ちるぞ!」
という現象でお悩みではないですか?
subject { some_model.change }
it { expect { subject }.to change(some_model.reload.changed, true) }
# some_model.reload.changedがtrueとなっているはずなのにfalseのまま
もしかするとchangeメソッドが原因かもしれません。
changeメソッドは括弧で括った場合、some_model.reloadが更新処理の実行前にしか評価されないからです。
詳細
まず、このitブロックは以下の順番で処理されます。
- changeメソッドのパラメータが評価される
- つまり、
some_model.reloadがこの時点で評価されます
- つまり、
- subjectが実行されて更新処理が走る
- 1で評価された時点のモデルのインスタンス変数
changedとsubject実行後のchangedを比較する
これ、model.reloadが最初に走っちゃってるんですよね。
更新処理実行後にmodel.reloadが走ってないと正しい結果は得られません。
ではどうすればいいのでしょう?
解決策
changeメソッドを以下のようにブロックで記述します。
そうするとsome_model.reloadはsubjectの実行前だけでなく実行後にもう一度実行されます。
change {some_model.reload.changed, true}
こうすることで、更新処理実行後にもインスタンスが評価されるので正しい結果が得られます。
参考