問題: なぜか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 リレーションを理解する」を読むとわかりやすいです。