問題: なぜかfailするテスト
it 'adds comment' do
post = Post.create(text: 'Hello!')
comment = Comment.new(text: 'Hi!')
expect{post.comments << comment}.to change{post.comments}.from([]).to([comment])
end
このテストを実行するとfailします。
result should have initially been [], but was #<ActiveRecord::Associations::CollectionProxy [#<Comment id: 1, text: "Hi!", post_id: 1, created_at: "2014-03-21 01:51:44", updated_at: "2014-03-21 01:51:44">]>
./spec/models/comment_spec.rb:7:in `block (2 levels) in <top (required)>'
なぜでしょうか?
failする原因: post.commentsは実は配列ではない
post.comments
は配列ではなくリレーションなので、比較される寸前までデータベースに問い合わせない、というのが主要な原因です。
post.comments << comment
の実行前にpost.comments
はいったんRSpec側に実際のfrom
の値として保存されますが、まだデータベースに問い合わせていません。
そして、post.comments << comment
の実行後にpost.comments
の値をfrom
とto
のそれぞれで評価します。
しかし、どちらもこのタイミングでデータベースに問い合わせるので、from
にもデータベースに保存されたcomment
が返ってきてしまいます。
結果として、「from
の値は[]
ではなかった」と判断されてfailするわけです。
解決策: 明示的に配列に変換する
change{}
の中をリレーションではなく、確実に配列として返すようにします。具体的にはpost.comments.to_a
です。
こうすれば、post.comments << comment
の実行前にデータベースへの問い合わせが発生し、[]
が実際のfrom
として保存されます。
it 'adds comment' do
post = Post.create(text: 'Hello!')
comment = Comment.new(text: 'Hi!')
expect{post.comments << comment}.to change{post.comments.to_a}.from([]).to([comment])
end
参考情報
この問題は前回のQiita記事のテストを書き直している最中に遭遇しました。(原因を特定するまでに結構時間がかかってしまった・・・)
しっかり読めていませんが、たぶん同じようなことを言っている記事がこれです。
リレーションに関しては以下の書籍の「056 リレーションを理解する」を読むとわかりやすいです。