【RSpec】モデルスペック
前提
RSpecのセットアップ済
FactoryBot導入済
実現したいこと
既存のTaskモデルのバリデーションをチェックしたい
文法に馴染みがなく、簡単なテストなはずなのに半日くらいかかってしまいました。
分かりにくかった部分だけ、復習していきます。
モデルスペックを作成する
% rails g rspec:model task
FactoryBotの設定
UserとTaskは1対多の関係です。なので、どちらもテスト用データを作成します。
email
とtitle
はユニーク制約をつけています。
FactoryBot.define do factory :user do sequence(:email) { |n| "tester#{n}@example.com" } password { 'password' } password_confirmation { 'password' } end end
一意である必要があるemailについては、sequence
メソッドを使います。
この構文で、ユーザーを生成する度にn
の数字を変えて一意のアドレスを作成できます。
FactoryBot.define do factory :task do sequence(:title, "title_1") content { 'Content' } status { 'todo' } #enumで定数を設定している。 deadline { Date.current.tomorrow } association :user end end
先程と同様、title
はユニーク制約をつけているので、sequence
メソッドを使用します。
最初は、sequence(:title) { |n| "Title #{n}" }
のようにしていたんですが、第二引数を渡すと.next
を呼んでくれて、連番をつけてくれるようです。便利。
String#next (Ruby 2.7.0 リファレンスマニュアル)
FactoryBot (旧FactoryGirl) の sequence と .next - Qiita
deadline { 1.week.from_now }
こっちのほうがいいかも
こんな機能があるのは知らなかった。。
Active Support コア拡張機能 - Railsガイド
association :user
これで、TaskとUserに関連があることを設定できる。便利。
FactoryBot省略設定
FactoryBotは通常、FactoryBot.create(略)
のようにテストデータを生成できますが、以下の設定をしておくと、FactoryBot
を省略できます。
RSpec.configure do |config| (略) config.include FactoryBot::Syntax::Methods #追記 end
Task Model Spec
テストデータ生成の準備が整ったので、Specのコードを書いていきます。
とりあえず今回は、以下の3つに絞ります。
- 全部の属性を登録したときvalid
- titleがないときにinvalid
- titleが重複したときinvalid
require 'rails_helper' RSpec.describe Task, type: :model do describe 'validation' do it "is valid with all attributes" do task = build(:task) #先の設定のおかげで、FactoryBot.build(:task)と書かなくてもOK expect(task).to be_valid expect(task.errors).to be_empty #エラーメッセージなし。 end it "is invalid without a title" do task_without_title = build(:task, title: nil) #titleはなるべくわかりやすくする。 expect(task_without_title).to be_invalid expect(task_without_title.errors[:title]).to eq ["can't be blank"] end it "is invalid with a duplicate title" do task = create(:task) #1つめのtaskはcreateで保存までする。 task_with_duplicated_title = build(:task, title: task.title) #2つめのtaskは1つめと同じ名前にする。 expect(task_with_duplicated_title).to be_invalid expect(task_with_duplicated_title.errors[:title]).to eq ["has already been taken"] end
全てに共通する流れは、
- Taskオブジェクトを生成
- 期待する挙動を検査