LoginSignup
20
17

More than 3 years have passed since last update.

【RSpec】言語非依存の壊れにくいバリデーションテストを作る

Last updated at Posted at 2020-01-12

はじめに

RSpecでバリデーションのテストを書くとき、下記のように書くことが多いと思います。

spec/models/user_spec.rb
  it "is invalid without a name" do
    user = User.new(name: nil)
    user.valid?
    expect(user.errors[:name]).to include("can't be blank")
  end

かのEverydayRailsにも同様のサンプルが掲載されています。

しかし、I18nを導入してエラーメッセージなどを日本語化したときにこのテストは通過しなくなってしまいます。
そこで、次の様に修正します。

spec/models/user_spec.rb
  it "is invalid without a name" do
    user = User.new(name: nil)
    user.valid?
    expect(user.errors[:name]).to include("を入力してください")
  end

これでテストを通過することが出来ました。

しかしこれでは言語設定を変更するたびにテストが壊れて修正が必要になってしまうという問題が残ります。

解決方法

I18nの導入

ここでは記述しません。
[初学者]Railsのi18nによる日本語化対応などが参考になると思います。

I18n.translate メソッドを使用する

Railsガイド-基本的な参照、スコープ、ネストしたキーの項によると、次のようにしてエラーメッセージを呼び出すことが出来ます。

I18n.translate "activerecord.errors.messages.record_invalid"

アプリケーションのlocaleを日本に指定している状態で、rails cで実行すると

>> I18n.translate "activerecord.errors.messages.record_invalid"
=> "バリデーションに失敗しました: %{errors}"

この様になります。
冒頭のバリデーションテストをこの方法で書き換えると次のようになります。

spec/models/user_spec.rb
  it "is invalid without a name" do
    user = User.new(name: nil)
    user.valid?
    expect(user.errors[:name]).to include(I18n.t('errors.messages.blank'))
  end

上記ではI18n.translateのエイリアスであるI18n.tを代わりに使用しています。
これで、言語設定が変わっても壊れないバリデーションテストを書くことができました。

エラーメッセージの呼び出し方色々

先程も参照したRailsガイド-基本的な参照、スコープ、ネストしたキーの項によると、下記の呼び出しはすべて等価ですので、好みの方法を使って書いてみてください。

I18n.t 'activerecord.errors.messages.record_invalid'
I18n.t 'errors.messages.record_invalid', scope: :activerecord
I18n.t :record_invalid, scope: 'activerecord.errors.messages'
I18n.t :record_invalid, scope: [:activerecord, :errors, :messages]

他のエラーメッセージを呼び出すには

Railsガイド-エラーメッセージ内での式展開の項に表でまとめられています。

I18n.translate "activerecord.errors.messages.record_invalid"record_invalidの部分を表のメッセージ列の値に置き換えることで他のエラーメッセージを呼び出すことが出来ます

引数付きのエラーメッセージ呼び出し

rails cI18n.t('errors.messages.too_short')を実行すると

>> I18n.t('errors.messages.too_short')
=> "は%{count}文字以上で入力してください"

このような式展開が使われていることから引数にcount: [最低文字数]を付加すれば良いと分かります。

>> I18n.t('errors.messages.too_short', count: 6)
=> "は6文字以上で入力してください"

以上からパスワード作成時などに6文字以上のバリデーションを設定しているときは、次のようにテストすることが出来ます。

spec/models/user_spec.rb
  it "is invalid with a shoot password" do
    user = User.new(password: 'a' * 5)
    user.valid?
    expect(user.errors[:password]).to include(I18n.t('errors.messages.too_short', count: 6))
  end

Railsガイド-エラーメッセージ内での式展開の項の表の式展開列に使用されている式展開が書かれているのでご参照ください。

他の方法(2020/1/24追記)

本稿のコメントで@jnchitoさんより、自身が以前書いたエントリで近い内容を紹介していたことを教えて頂きました。

どのフィールドにどの検証エラーが追加されたのかを、「表示言語やエラーメッセージに依存しない形で」テストする方法

このエントリで紹介した方法との違い

@jnchitoさんの方法ではi18nを介さず、エラーの種類自体をチェックする方式となっています。一方で私が紹介したのは、i18nで翻訳後のエラーメッセージをチェックする方式となっています。

この2つの方式の違いが出るのは、例えばja.ymlの中に翻訳後のエラーメッセージが登録されていなかったときです。

config/locale/ja.yml
errors:
    format: "%{attribute}%{message}"
    messages:
      accepted: を受諾してください
      # ja.errors.messages.blankをコメントアウト
      #blank: を入力してください
      confirmation: と%{attribute}の入力が一致しません

この状態でテストを実行すると@jnchitoさんの方法ではテストを通過します。
一方、私が紹介した方法では下記のようにエラーが発生します。

 1) User is invalid without name
     Failure/Error: expect(user.errors[:name]).to include(I18n.t('errors.messages.blank'))
       expected ["translation missing: ja.activerecord.errors.models.user.attributes.email.blank"] to include "translation missing: ja.errors.messages.blank"
     # ./spec/models/user_spec.rb:18:in `block (3 levels) in <top (required)>'
     # -e:1:in `<main>'

個人的には@jnchitoさんの方法では

  • エラーの種類のチェックとviewに表示されている翻訳後のメッセージのチェックを分離できる
  • より短くシンプルなテストを書ける

などのメリットがあると思います。

一方でこの記事で紹介した方法では

  • 翻訳後のエラーメッセージがja.yml等に正しく登録されていることをチェックできる
  • 登録されているエラーメッセージがviewに正しく表示されていることをチェックできる

という点でメリットがあると思います。(他にもまだメリット、デメリットはあるかもしれません。)

細かい違いですが、その時やりたいことや好みに応じて使い分けると良いかと思います。

おわりに

I18nで日本語化した時に、バリデーションテストでエラーメッセージをベタ書きしてチェックする方法以外のやり方を調べましたが、ピンポイントで書かれている記事が見つからなかったため書いてみました。
同じ疑問を持った誰かのお役に立てればと思います。

他の方法(2020/1/24追記)で紹介した通り、近い内容のエントリが既にありました。ご指摘して頂いた@jnchitoさんありがとうございます。

参考

20
17
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
17