ずみーBlog

元クラゲ研究者(見習い)の92年生まれがエンジニアを目指しながら日々寄り道するブログです。

【RSpec】モデル単体テストで、全カラムに対して.eachで同じ操作を実行したい

Rails単体テスト書いてます。 例えばpresence: trueみたいな、たくさんのカラムに適用されているバリデーションって、一つ一つ書くのがめんどくさいですよね。 全カラムを配列で取得し、each文で回して、presence: trueバリデーションを一括でテストしてみました。

今回テストするモデル

item.rb

Column Type Options
user references null: false, foreign_key: true
name string null: false
information_text text null: false
category_id integer null: false
status_id integer null: false
shipping_fee_status_id integer null: false
prefecture_id integer null: false
scheduled_delivery_id integer null: false
sell_price integer null: false

準備

FactoryBot

spec/factories/item.rb factory_bot_railsfakerrack-testを利用してテストデータを自動buildできるようにする。

rack-testで添付ファイルを使用するやり方はこちらの記事を参照

FactoryBot.define do
  factory :item do
    association :user
    name                   { Faker::Lorem.word }
    information_text       { Faker::Lorem.sentence }
    category_id            { Faker::Number.within(range: 1..10) }
    status_id              { Faker::Number.within(range: 1..6) }
    shipping_fee_status_id { Faker::Number.within(range: 1..2) }
    prefecture_id          { Faker::Number.within(range: 1..47) }
    scheduled_delivery_id  { Faker::Number.within(range: 1..3) }
    sell_price             { Faker::Number.within(range: 300..9999999) }
    image                  { Rack::Test::UploadedFile.new(File.join(Rails.root, 'public/images/test_image.jpg')) }
  end
end

helper

次に、itemモデルのレコードとカラム名を文字列で渡すと、そのカラムをnilにしてエラーチェックしてくれるヘルパーを定義します。

spec/support/item_nil_support.rb

module ItemNilSupport
  def item_nil_validation(item, col, nihongo)
    item[col.to_sym] = nil
    item.valid?
    expect(@item.errors.full_messages).to include("#{nihongo}を入力してください")
  end
end

ヘルパーメソッドの使い方についてはこちらの記事にまとめました。

カラムを配列で取得し、eachで回す

13行目:カラム名の配列はcolumn_namesメソッドで取得できます。今回チェックする必要の無いuser_idやcreated_dateなどは引き算で除いています。 eachのループの中でexample、すなわちit "..." do endを定義し、その中で先ほどのヘルパーを呼び出しています。

item_spec.rb

require 'rails_helper'

RSpec.describe Item, type: :model do
  before do
    @item = FactoryBot.build(:item)
  end

  describe '商品新規登録' do
  #省略
    context '新規登録がうまくいかないとき' do
      context 'nilのカラムが原因のとき' do
        #はじめにnilにしたいカラムの配列を作成する
        columns = Item.column_names - ["user_id", "id", "created_at", "updated_at"]
        columns.each do |col|
          nihongo = I18n.t "activerecord.attributes.item.#{col}"
          it "#{nihongo}が無ければ登録できない" do
            item_nil_validation(@item, col, nihongo)
          end
        end
  #省略
end

補足 (上の例のnihongoについて)

[11] pry(main)> I18n.t 'activerecord.attributes.user.nickname'
=> "ニックネーム"

今回のアプリケーションはデフォルトのlocaleがjaになっており、翻訳用のYAMLファイルは以下のようになっていました。

ja:
  activerecord:
    attributes:
      user:
        nickname: ニックネーム

この時、I18nクラスのtメソッドを用いることで、指定の単語に対する翻訳を取得することができます。上の例では、ヘルパーメソッド内で、渡されたカラム名ごとにエラーメッセージを動的に出力させるのに使っています。

item_spec.rb

# colはループ変数で、単一のカラム名が入る
nihongo = I18n.t "activerecord.attributes.item.#{col}"

spec/support/item_nil_support.rb

expect(@item.errors.full_messages).to include("#{nihongo}を入力してください")

[10/24追記] クラスがActiveRecordではなくAcriveModelを継承している場合

以下のように、モデルがActiveModelをincludeしている場合は、日本語訳の取得方法が異なります。

class Transaction
  include ActiveModel::Model
  attr_accessor :user, :item, :token, :postal_code, :prefecture_id, :city, :addresses, :building, :phone_number
  #省略
end

YAMLファイルと日本語訳の取得方法はそれぞれ以下のようになります。

ja:
  activerecord:
    #省略
  activemodel:
    attributes:
      transaction:
        user: 購入者
        item: 商品
        #省略
[1] pry(main)> Transaction.human_attribute_name("user")
=> "購入者"