RSpec入門・演習2
2023.10.16
いよいよRSpecの実際の使い方に入っていく。
sequenceについて。以下の記事がとても参考になった。二通り書き方があるのね。変えたい数字や文字が値の末尾についてる場合はブロック無しでも書けると。
インプット終了。ここから課題に入る。
status(integer)が未入力なのを表すには''
なのかnil
なのか?
nil
は値なし、''
は空の値、だから''
なのかなあ。
早速テストしたけど一個引っかかったな。タイトルが被ったときにinvalidになるかってやつ。
今日はここまでにして、明日そこの解明からやろう。
2023.10.17
昨日引っかかったところの解消をする。
it 'titleが被った場合にuniqueのバリデーションが機能してinvalidになるか' do
user = User.create(email: 'user@example.com', password: 'password')
task1 = user.tasks.new(title: 'aaa', content: 'aaa', status: :todo)
task2 = user.tasks.new(title: 'aaa', content: 'bbb', status: :todo)
expect(task2).to be_invalid
end
このケースがエラーになってしまう。
う〜ん見直しても原因がわからない…ちゃんとタイトルが重複するようにしてるから、invalid
になるはずなんだけどなあ。
あ〜なるほど!task1がnew
しただけでDBに保存されてないからなんだ!そりゃそうかインスタンス生成するだけならバリデーションも何もないな確かに。create
にしないとだめだ。
task1 = user.tasks.create(...)
としたところ、今度は以下のエラーが出た。
ActiveRecord::RecordNotSaved:
You cannot call create unless the parent
user = User.create(...)
って書いてあるのになんで!?とさすがに意味不明だったのでロボらんてくんに相談したところ、ユーザーの保存がうまくいってない可能性があるのでコンソールでやってみてと言われた。
やってみたら、ActiveRecord::StatementInvalid: Could not find table 'users'
というエラーが。確かに保存できてない。
にしても、usersテーブルがない??モデルファイルもあるしデータベーススキーマを見てもできてるっぽいのに???
と思いさらにロボらんてくんに相談すると、rails db:migrate
してみ、とのこと。やってから再度コンソールでユーザーの保存を試みたところ、以下の結果に。
root@46555f1c7713:/sample_app_for_rspec# bin/rails c
Loading development environment (Rails 7.0.4.3)
[1] pry(main)> user = User.create(email: 'user@example.com', password: 'password')
TRANSACTION (0.0ms) begin transaction
User Exists? (0.3ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = ? LIMIT ? [["email", "user@example.com"], ["LIMIT", 1]]
TRANSACTION (0.0ms) rollback transaction
=> #<User:0x0000ffff93dde3c8 id: nil, email: "user@example.com", crypted_password: "$2a$10$Ur0fsnWVZcliSGBnpFuXku6sGWd6rZ.V5BFxH6TP7oCyfFc7W/zLG", salt: "g3-zJizDFWLQcMXzcB5C", created_at: nil, updated_at: nil>
なんか最終的には失敗してるっぽいけど無事テーブルは存在するようになったみたいだ。理屈はわからないけどマイグレーションをする必要があったらしい。
ただ、これ保存できてる?とロボらんくんにきいたところやはりできてないとのこと。user.errors.full_messages
でエラーメッセージ確認してみとアドバイスを貰ったので即実行。すると以下の結果に。
[2] pry(main)> user.errors.full_messages
=> ["Password confirmation can't be blank"]
password_confirmation!!
確かに入力してない!
…これたぶんわざと見本に書かなかったな??まんまと術中にハマってしまった…。
時間なので本日はここまで。原因がわかったので、明日修正して先ヘ進む。
2023.10.18
昨日判明したエラー原因の解消から。
修正して無事テスト成功。現段階のコードをコミットして、次のfactory botの使用に移る。
セットアップと実際のデータ作成を行う。
RSpecの設定はrails_helperに書くと。require
で読み込んでるやつだな。
userはたぶんこれでOK。問題はtaskだな。アソシエーションで紐づいたオブジェクトの生成をどうやるかってことか。
まずはシンプルにuser.create(:task)
という書き方を試してみるか。
とりあえずテストは知らせてみたところ、以下のエラーが出た。
Failure/Error:
factory :task do
sequence(:title) { |n| "task#{n}"}
content { 'aaa' }
status { :todo }
end
FactoryBot::DuplicateDefinitionError:
Factory already registered: task
taskのfactoryがダブってる??確認してみるか。
ん〜??そんな事無いと思うんだけど…。
ひょっとして一つのモデルにつき一つFactoryBot.define
しなきゃいけないとか??
試してみたけど変わらんな。どういうことだろう。
あ〜〜わかった!FactoryBotのコードを書くためのファイルが別にあった!で、そっちにtaskのfactoryの雛形がすでにある。だからダブったんだ。コードをそっちに移設しよう。
移設したら無事ダブりの問題は解決した。ただやっぱりuser.create(:task)
って書き方はできないみたいだ。やり方調べよう。まずは公式ドキュメントをあたる。
ん〜それっぽい記述は見つけたけどちょっとよくわからない。技術記事に頼るか。
ほ〜association :関連モデル名
だけでいいんだ。
あ、なるほどfacory名と関連名が同じ場合は関連名だけ書けばいいんだ。さっきの公式ドキュメントに出てきたコードの意味がわかった。
# ↓これと
FactoryBot.define do
factory :task do
content { 'aaa' }
association :user
end
end
# ↓これは同じ意味
FactoryBot.define do
factory :task do
content { 'aaa' }
user
end
end
ひょっとしてこれわざわざユーザーの生成を別でする必要なかったりする?task生成すると自動で生成されるのでは?試してみるか。
お、いけたっぽい。ということでユーザー生成コードは削除して、適宜属性の値をオーバーライドしつつテストコードを修正していこう。
「ちゃんとinvalidになるか?」系のテストが全部エラーになった😇
見てみたら
ActiveRecord::RecordInvalid:
Validation failed: Title can't be blank
って言われてる。
おそらく「invalidになるか?」系は実際に保存(create) しちゃいけないんだな。インスタンス作成までに留めるべき。
修正してもう一度テスト。
OK、通った。逆に「ちゃんとvalidになるか」系はどっちのほうがいいんだろうか。とりあえずcreateにしておくか。
ここまでの状態をコミットしたので、課題完了にして解答例を確認する。
問題なければそのまま、あれば修正して最終コミット→プルリク。
解答例だと異なっている点
- sequenceの書き方
- taskにdeadlineを設定してる
- taskとuserでFactoryBotのファイルを分けてる
- 必要なところ以外はcreateでなくbuildを使ってる
- invalidであることが期待されるオブジェクトにはわかりやすい名前をつけている
- titleがかぶる場合とそうでない場合が並ぶような順番になってる
- title被り関連についてはチェックするのは後から作成するインスタンスのほうだけでOK
以上の点を修正する。
修正したので再度テスト。
OK!コミット→プルリクする。
RSpecの基本的な使い方は一度まとめといたほうが良いかも。