存在制約 shared_example: 小林研 Rails Tips (2)

はじめに

Rails Tips の 2 回目です。今日から数回は model のテストを簡単に書くための shared_example の紹介です。私の Shared example の雛形 (lib/templates/rspec/model/model_spec.rb)はこんな感じになっています。

require "rails_helper"

# RSpec.describe <%= class_name %>, type: :model do
#  let(:<%= singular_table_name %>) { <%= plural_table_name %> :<%= singular_table_name %> }
#  let(:can_delete) { <%= plural_table_name %> :can_delete }

#  context "属性に関する共通テスト" do
#    subject { can_delete }
#    let(:another_object) { <%= plural_table_name %> :another_object }

#    it_behaves_like "存在制約", %i[]
#    it_behaves_like "一意制約", %i[]
#    it_behaves_like "複合一意制約", %i[]
#    it_behaves_like "削除可能制約"
#    it_behaves_like "削除不可制約"
#    it_behaves_like "関連確認", :<%= singular_table_name %>, has_many: %i[], has_one: %i[], children: :optional, child: :optional
#    it_behaves_like "親削除時に自分も削除", :<%= singular_table_name %>, %i[has_many has_one]
#    it_behaves_like "親は削除不可", :<%= singular_table_name %>, %i[has_many has_one]
#    it_behaves_like "親削除時にnullを設定", :<%= singular_table_name %>, %i[has_many has_one]
#  end

属性やリレーションなどのモデルに関するテストはほとんど共通になるので、全部 shared_example で記述しています。今日からはこれを一つずつ紹介していきます。今日は最初の存在制約になります。

存在制約

モデルの属性のうち、null を許可しないものをチェックするための shared_example です。例えば、blog モデルの title が null になっては困る時には、blog_spec.rb は以下のようなテストになります。また、boolean は少し実装が異なるので enable も not null の制約をつけることにしてみます。

require "rails_helper"

RSpec.describe Blog, type: :model do
  let(:blog) { blobs :blog }
  let(:can_delete) { blogs :can_delete }

  context "属性に関する共通テスト" do
    subject { can_delete }

    it_behaves_like "存在制約", %i[title enable]
  end
end

これを実現するための「存在制約」 shared example は以下のようになります。こちらは spec/supports/shared_example.rb に記載されています。

shared_examples_for "存在制約" do |keys|
  it { is_expected.to be_valid }

  keys&.each do |key|
    it "#{key} の内容が nil のとき、エラーになること" do
      subject[key] = nil
      expect(subject).not_to be_valid
    end
  end
end

fixtures の状態でモデルが valid であることは、一番最初に確認します。あとは指定されたキーを一つずつ個別に nil にしたときに valid でなくなることを確認しているだけです。keys を一つも指定しなかった場合には、fixtures が正しく作成できているかのテストになります。このため、全ての model のテストで必ずこの shared_example は実行します。

正しく動作させるための実装

属性が null にならないことを検証することになるので、以下のコードを実装に記述することでテストを追加させることができます。boolean 以外の属性については、presence: true で検証ができます。ただし、boolean の属性の場合値が false の場合に presence が true になりません。そこで、boolean 属性の場合には、inclusion で true または false のどちらかの値を取ることという検証にする必要があります(こちらは忘れてよくハマります)。

class Blog
  validates :title, presence: true
  validates :enable, inclusion: {in: [true, false]}
end

おわりに

2回目にしてかなり短いブログになりました。毎日更新なので小出しになるのは許してください。

P.S. 公開後に boolean の話を追記しました。