【Rails】rspecでmodelのconcernを単体テストする方法

ことの始まり

結構大きめの複数modelで使うメソッドを作る必要があったから、concernにまとめちゃおうと思ったものの、メソッドの単体テストどうしよう…
ということで、調べたら一応できた。
最近rspec周りの記事が多いなぁ

modelのconcern作成

例えばuser modelがあり、first_namelast_nameをテーブルに持っているとする。

class User < ApplicationRecord
# == Schema Information
#
# Table name: users
#
#  id        :integer
# first_name :string
# last_name  :string
end

first_namelast_nameを別々に保存はしたいものの、実際に出力する際は一緒にしたfull_nameを使いたい。
他のmodelでも使うことがありそうだから、concernに切り分けておく。

module FullNameCreator
  extend ActiveSupport::Concern

  private

  def full_name(str_1:, str_2:)
    "#{str_1} #{str_2}"  # 半角スペースを入れたnameを返す
  end
end

作ったconcernmodelで使う。

class User < ApplicationRecord
  include FullNameCreator

  def name
    full_name(str_1: first_name, str_2: last_name)
  end
end

これで、他のクラス(例えばCustomer modelとか)でも作成したconcernを使うことができるようになる。

concernの単体テスト

rspecでconcernのメソッドが期待値通りの動きをしているかの確認をするには、User modelのテストでnameメソッドが期待値を返しているかで確認をすることもできるが、他のmodelでも確認が必要になる。
複数modelで使っていると面倒なので、concernで単体テストを実行したい。
分けて書くのは面倒なのでコメントアウトで解説を入れながらコードを書いてみる。

require 'rails_helper'
require 'spec_helper'

RSpec.describe FullNameCreator, type: :model do
  # 今回確認したいメソッド
  describe '.full_name' do
    # まずはテストに必要なテーブルを作成する
    before(:all) do
      m = ActiveRecord::Migration.new
      m.verbose = false
      m.create_table :full_name_tests do |t|
        t.string :first_name
        t.string :last_name
      end
    end

    # テストが終わったらテーブルを削除
    after(:all) do
      m = ActiveRecord::Migration.new
      m.verbose = false
      m.drop_table :full_name_tests
    end

    # テスト用のモデルを作成(対象のconcernをincludeしておく)
    class FullNameTest < ApplicationRecord
      include FullNameCreator
    end

    # テスト用のクラスを作成(入力値は後で指定するのでインスタンス変数にしておく)
    let(:user) { FullNameTest.new(first_name: first_str, last_name: second_str) }

    # 作成したクラスをテスト用に保存
    before {user.save}

    # テストしたいメソッドをsubjectにする(引数はメソッド名に続けて記載しておく)
    subject { user.send(:full_name, str_1: first_str, str_2: second_str) }

    context 'return full name' do
      # 先ほど使ったインスタンス変数に代入
      let(:first_str) { "yasa" }
      let(:second_str) { "gori" }

      it { is_expected.to eq("yasa gori") }
    end
  end
end

これで、concernの単体テストができる。
今回はメソッドが1つしかないのでメソッドの中でテーブルを作成したが、複数のメソッドを確認する場合は各メソッドごとにテーブルをcreate, dropすると時間がかかるので、まとめてやった方が良い。

まとめ

concernの単体テストは下準備が非常に多いので実際やる思った以上に面倒だった…