見出し画像

【メモ】ActiveRecordでboolean型のカラムに必須バリデーションが設定されてるかテストする

久しぶりにやったら忘れてたのでメモ。
RailsでRspecとshoulda-matchersを利用してテストコードを書く時、あるモデルのboolean型に必須バリデーション(という表現であってるのだろうか)が設定されているかをテストするにはどうするか、というお話です。

モデル側の設定

まず例として、RailsでExampleモデルというのがあり、watchedというカラムを持っているとします。
Railsであるモデルのあるカラムに値が入ることを必須とする場合、以下のように『validates :カラム名, presence: true』と書きます。

class Example < ApplicationRecord
 validates :watched, presence: true
end

基本はこれでOK。
が、このカラムがboolean型であった場合、上のコードは動きません。
何故かというと、『presence: true』を検証している元ソースRailsガイドを見ると分かるのですが、実際に入力された値を.blank?メソッドによって検証しているのが原因となっています。
以下、Railsガイドより引用。

false.blank?は常にtrueなので、真偽値に対してこのメソッドを使うと正しい結果が得られません。真偽値の存在をチェックしたい場合は、validates :field_name, inclusion: { in: [true, false] }を使う必要があります。

ということで、真偽値の存在をチェックしたい場合、正しくは以下の通りになります。

class Example < ApplicationRecord
 validates :watched, inclusion: { in: [true, false] }
end

この辺はRailsガイドにも書かれてるところなので、サクッと流して次にいきましょう。

テスト側の設定

さて、問題のテスト側に行きましょう。
テストで使用するshoulda-matchersはREADMEにもありますが、テストコードをワンライナーでより手軽に実装できるように補佐するgemです。詳しい使い方は同じくREADMEをご参照いただければ。

使用するmatchersはバリデーションの種類に応じて使い分けます。今回はinclusionが設定されているかを検証したいので、『validate_inclusion_of』のmatchersを使いましょう。
コードとしては以下のような感じになると思います。

require 'rails_helper'

RSpec.describe Example, type: :model do
 describe 'バリデーション' do
   it { is_expected.to validate_inclusion_of(:watched).in_array([true, false]) }
 end
end

ですがこれでテストを実行すると、恐らく以下のような警告文が表示されると思います。

You are using `validate_inclusion_of` to assert that a boolean column allows
boolean values and disallows non-boolean ones. Be aware that it is not possible
to fully test this, as boolean columns will automatically convert non-boolean
values to boolean ones. Hence, you should consider removing this test.

要約すると、ActiveRecordによってboolean型でない値であってもboolean型にキャストされてしまうため、このテストコードではboolean型の値しか受け付けないようになっているか検証できない、つまりテストとして無意味なのでテストを削除するように促しているということですね。

ではどうやったらテスト出来るのかということですが、元ソースであるvalidate_inclusion_of_matcher.rbの中を見てみると、代替案を提示してくれています。

# We discourage using `validate_inclusion_of` with boolean columns. In
# fact, there is never a case where a boolean column will be anything but
# true, false, or nil, as ActiveRecord will type-cast an incoming value to
# one of these three values. That means there isn't any way we can refute
# this logic in a test. Hence, this will produce a warning:
#
#     it do
#       should validate_inclusion_of(:imported).
#         in_array([true, false])
#     end
#
# The only case where `validate_inclusion_of` *could* be appropriate is
# for ensuring that a boolean column accepts nil, but we recommend
# using `allow_value` instead, like this:
#
#     it { should allow_value(nil).for(:imported) }

これまた要約すると、先述したようにActiveRecordは入力された値を型キャストするため、trueかfalseかnilのいずれかの値にしかならないので、期待するようなテストは行えないとあります。
一応『validate_inclusion_of』を適切に動かそうとするならnilを受け付けるようにすればいいけれど、それより『allow_value』を使うことを推奨するよ、と。

基本的にboolean型のカラムにnilが入り込むことを許可するシーンというのはあんまり無いと思うので、推奨される通りallow_valueを使ってテストを書き直しましょう。
trueとfalseが入ることは許可して、nilが入ることは許可しない。それをテストコードにすると、以下のような感じになると思います。

require 'rails_helper'

RSpec.describe Example, type: :model do
 describe 'バリデーション' do
   it { is_expected.to allow_value(true).for(:watched) }
   it { is_expected.to allow_value(false).for(:watched) }
   it { is_expected.not_to allow_value(nil).for(:watched) }
 end
end

これでOK!

この記事が気に入ったらサポートをしてみませんか?