blog.kotamiyake.me

為せば成る、為さねば成らぬ何事も

Railsはデフォルトで基本的なバリデーション機能を提供してくれています。

ただ提供されていないバリデーションを追加したいこともあります。そんなとき、一度しか登場しない機能であれば validate などで直にバリデーションを書いてしまいます。

ただ複数箇所で同じようなバリデーションを使いまわしたい場合があります。そこで登場するのが Custom Validators です。

Custom Validator の実装方法は Rails Guides を読めばわかるので割愛しますが、今回はそのテスト方法を紹介したいと思います。

今回する方法は以下の環境で試していますが、それほど古い環境でなければ同じように動作するかと思います。

  • ruby 2.5.3p105
  • Rails 5.2.2
  • RSpec 3.8

単純に実際に使用しているモデルをテストすればいいかと思うかもしれませんが、それではモデルと密に繋がってしまい、Custom Validator のテストがモデルに依存してしまいます。

そこで今回はバリデーションを定義したモックとなるモデルを生成してテストします。

まずはテスト対象となる Custom Validator を作成します。バリデーションの要件は「全角カタカナ」のみを含む、とします。

全角カタカナのチェックは 全角カタカナにのみマッチする正規表現 – Qiita を参考にしました。

class KatakanaValidator < ActiveModel::EachValidator
  def validate_each(record, attr_name, value)
    unless value =~ /\A[\p{katakana} ー-&&[^ -~。-゚]]+\z/i
      record.errors.add(attr_name, :katakana)
    end
  end
end

テストは 【Rails】まだValidatorのテストで消耗してるの? – Qiita を参考にしています。

上記の筆者の方がgemを作成していただいているのですが、それほど規模が大きくないこと、メンテナンスの継続性なども含めて自身で実装することにしました。

RSpecの使い方などは理解しているという前提で話を進めさせていただきます。そのあたりはいろいろな記事で紹介されているかと思いますので、そちらをご参照ください。

複数のテストで使うことからsupport/以下に共通モジュールとしてモック生成モジュールを作成します。

module CustomValidatorHelper
  def build_mock(attr_name , validator: )
    raise ArgumentError if attr_name.blank? || validator.blank?

    Struct.new(attr_name) do
      include ActiveModel::Validations

      def self.name
        'DummyModel'
      end

      validates attr_name, validator => true
    end
  end
end

RSpec.configure do |config|
  config.include CustomValidatorHelper, type: :model
end

この記事を書いていて、メソッドの名前は考え直したほうが良いなと思いました…が、とりあえずそこはスルーして読み進めてください。

それでは実際のテストコードです。

require 'rails_helper'

RSpec.describe KatakanaValidator, type: :model do
  describe '#validate_each' do
    it 'is valid' do
      mock_model = build_mock(:name_kana, validator: :katakana).new('カタカナ')
      expect(mock_model).to be_valid
    end

    it 'is not valid' do
      mock_model = build_mock(:name_kana, validator: :katakana).new('カタカナa')
      expect(mock_model).not_to be_valid
      expect(mock_model.errors).to be_added(:name_kana, :katakana)
    end
  end
end

これでモデルに依存せずに Custom Validator のテストを書けるようになりました。

テストはできるだけ依存を減らして、単体で機能するようにすると影響を受けずに安定したテストになるかと思います。

なのでそういったことも意識してテストを書くようにすると安定したサービスを開発できるのではないでしょうか。

また上記のコードは色々とリファクタリングできる要素があるかと思いますが、テストはあまりDRYを意識するよりは愚直な実装のほうが後から見た時にわかりやすくて良いかなと思うので、その辺のバランスも考えてテストコードを書いていくと、よりメンテナンスしやすいテストになりそうです。

以上、Rails の Custom Validator をテストする方法を紹介しました。