このページの内容は以下のリポジトリに1日遅れで反映されます(記事執筆前に前日分をコミットしています)。
https://github.com/hkob/hkob_blog
はじめに
Rails Tips の 55 回目です。model に関してテストをします。
7.3.3 バリデーションとエラーメッセージの表示
Rails ガイドではある程度動いてからバリデーションなどを追加していました。今回は、モデルの段階でちゃんとテストを実施しましょう。article_spec.rb
の先頭部分を以下のように修正します。ここでは、最初に title と body が空の時にバリデーションに引っかかることを確認します。属性に関する共通テストのうち、存在制約と削除可能制約を残します。
require "rails_helper" RSpec.describe Article, type: :model do let(:article) { articles :article1 } let(:can_delete) { articles :can_delete } context "属性に関する共通テスト" do subject { can_delete } it_behaves_like "存在制約", %i[title body] it_behaves_like "削除可能制約" end (以下のコメントは中略) end
使っている shared example はここで説明していました。ここに書かれているそれぞれの shared example を spec/support/shared_exmaple.rb
に記述しておきます。
ここで残っているのは articles の fixture data です。これらは、spec/fixtures の下に yaml ファイルとして用意します。
mkdir -p spec/fixtures touch spec/fixtures/articles.yml
この状態で、yml を保存したところ、guard で以下のようなエラーが出てしまいました。singularize が見つかっていません。これは単数系・複数形を変換する処理を担っている "active_support/inflector"
が読み込まれていないためです。
> [#] undefined method `singularize' for an instance of String
このため、Guardfile の先頭に以下のように require を追加します。
require "active_support/inflector" guard :rspec, cmd: "bundle exec rspec" do
これでエラーが以下のように変わります。can_delete という fixture が存在しないためです。
No fixture named 'can_delete' found for fixture set 'articles'
上のテストで用意した article1 と can_delete の fixtures を準備することにします。
article1: title: "This is the first article" body: "This is the body of the first article" can_delete: title: "This is the second article that can be deleted" body: "This is the body of the second article"
エラーは以下のようになりました。4つのテストのうち2つに失敗しています。
Article 属性に関する共通テスト behaves like 存在制約 is expected to be valid title の内容が nil のとき、エラーになること (FAILED - 1) body の内容が nil のとき、エラーになること (FAILED - 2) behaves like 削除可能制約 削除できること Failures: 1) Article 属性に関する共通テスト behaves like 存在制約 title の内容が nil のとき、エラーになること Failure/Error: expect(subject).not_to be_valid expected #<Article id: 158250001, title: nil, body: "This is the body of the second article", created_at: "2024-01-23 20:50:13.877383000 +0900", updated_at: "2024-01-23 20:50:13.877383000 +0900"> not to be valid Shared Example Group: "存在制約" called from ./spec/models/article_spec.rb:10 # ./spec/support/shared_example.rb:10:in `block (3 levels) in <main>' 2) Article 属性に関する共通テスト behaves like 存在制約 body の内容が nil のとき、エラーになること Failure/Error: expect(subject).not_to be_valid expected #<Article id: 158250001, title: "This is the second article that can be deleted", body: nil, created_at: "2024-01-23 20:50:13.877383000 +0900", updated_at: "2024-01-23 20:50:13.877383000 +0900"> not to be valid Shared Example Group: "存在制約" called from ./spec/models/article_spec.rb:10 # ./spec/support/shared_example.rb:10:in `block (3 levels) in <main>' Finished in 0.04556 seconds (files took 1.54 seconds to load) 4 examples, 2 failures
このテストを通過させるために article.rb
に validates を追加します。
class Article < ApplicationRecord validates :title, presence: true validates :body, presence: true end
7.3.3 では body の最低文字数が 10 文字という制約をつけていました。これも実装の前にテストに追加しましょう。先ほどの属性に関する共通テストの中に追加します。境界値チェックということで、10文字で OK、9文字で NG であることを確認すればよいです。
describe "body length check" do context "body is 10 characters" do before { subject.body = "a" * 10 } it { is_expected.to be_valid } end context "body is 9 characters" do before { subject.body = "a" * 9 } it { is_expected.to be_invalid } end end
当然ながらまだ制約を追加していないので、このような結果になりました。
1) Article 属性に関する共通テスト body length check body is 9 characters is expected to be invalid Failure/Error: it { is_expected.to be_invalid } expected `#<Article id: 158250001, title: "This is the second article that can be deleted", body: "aaaaaaaaa", ...reated_at: "2024-01-23 21:14:18.311106000 +0900", updated_at: "2024-01-23 21:14:18.311106000 +0900">.invalid?` to be truthy, got false # ./spec/models/article_spec.rb:21:in `block (5 levels) in <top (required)>'
このテストを通過させるために、実装を修正します。
validates :body, presence: true, length: { minimum: 10 }
これでテストが通過しました。結果は以下のようになります。
Article 属性に関する共通テスト behaves like 存在制約 is expected to be valid title の内容が nil のとき、エラーになること body の内容が nil のとき、エラーになること behaves like 削除可能制約 削除できること body length check body is 10 characters is expected to be valid body is 9 characters is expected to be invalid Finished in 0.07829 seconds (files took 1.54 seconds to load) 6 examples, 0 failures
console で確認
せっかくなので console でエラーを確認してみましょう。 bin/rails c
として Rails console を起動します。
$ bin/rails c Loading development environment (Rails 7.1.2) irb: warn: can't alias context from irb_context. irb(main):001>
ここで title に ABC、body に DEF として作成してみます。
article = Article.new title: "ABC", body: "DEF" => #<Article:0x000000012acd9df0 id: nil, title: "ABC", body: "DEF", created_at: nil, updated_at: nil>
保存すると失敗しています。エラーメッセージを確認すると何でエラーが出たかがわかります。以前、locales/ja.yml
を用意したため、メッセージが正しく日本語化されていました。便利ですね。
article.save => false irb(main):003> article.errors.messages => {:body=>["は10文字以上で入力してください"]}
せっかくなので title も nil にして確認してみます。title の方にもエラーが追加されたのがわかります。
article.title = nil; article.save => false article.errors.messages => {:title=>["を入力してください"], :body=>["は10文字以上で入力してください"]}
ついでに body も nil にしてみましょう。body は二つの validation に引っかかったことになるので、メッセージが配列になっていることがわかります。
article.body = nil; article.save => false article.errors.messages => {:title=>["を入力してください"], :body=>["を入力してください", "は10文字以上で入力してください"]}
おわりに
今日は、モデルの属性に関するテストを実施しました。以前、locales を設定していたので、エラーメッセージが自動的に日本語化されていたことも確認できました。