Programming Journal

学習したことの整理用です。

【RSpec】モデルスペック

前提

  • RSpecのセットアップ済  

  • FactoryBot導入済

実現したいこと

既存のTaskモデルのバリデーションをチェックしたい

文法に馴染みがなく、簡単なテストなはずなのに半日くらいかかってしまいました。
分かりにくかった部分だけ、復習していきます。

モデルスペックを作成する

% rails g rspec:model task

FactoryBotの設定

  • UserとTaskは1対多の関係です。なので、どちらもテスト用データを作成します。

  • emailtitleはユニーク制約をつけています。

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オブジェクトを生成
  • 期待する挙動を検査